アーカイブ:C言語において、NULLと0 と‘\0’の違いは・・・

高木です。おはようございます。

以前にも「_Pragma演算子ではまりました」というアーカイブ記事を掲載したことがありますが、今回も別ブログのアーカイブとなります。
元になったブログは http://クローバーフィールド.jp/ です。
このブログを今後どうするかは未定ですが、すでに更新が止まって久しいので、有益と思われる記事は可能な限りこちらに再掲載したいと考えます。


2016年2月5日の記事です。

他サイトの情報を名指しで批判するのも気が引けますが、中には見るに見かねてというか、言わずにおれない状況もあるものです。
今回取り上げるのもそのような状況からです。

Yahoo!知恵袋のC言語のカテゴリで、カテゴリQ&Aランキング上位に入っているこのページがまさにそれです。
「C言語において、NULL と 0 と ‘\0’ の違いは・・・」

比較的古いQ&Aですが、ひとこと言っておかないわけにはいきませんね。

質問者はわからないから質問しているのでとくに問題はありません。
けれども、わかっていない回答者が多すぎることに問題があります。
しかも、間違った回答がベストアンサーになっていたりします。

まず、質問は「C言語において」とありますので、C++と混同した回答をするのは論外です(参考程度にC++の話をする程度ならいいでしょうけど)。
CとC++は別の言語ですので、細かな部分に差異があるのです。

それでは、実際はどうなのかを見ていくことにしましょう。

最初にNULLですが、これは空ポインタ定数に展開されるマクロです。
「空ポインタ定数」とは何かというと、値0を持つ整数定数式、またはそれをvoid*にキャストした定数式のことです。

空ポインタ定数の具体例を挙げましょう。
0でも0x00でも1-1でも’\0’でもかまいませんし、それらをvoid*にキャストした(void*)0でも(void*)0x00でも(void*)(1-1)でも(void*)’\0’でもいいのです。

実際のNULLマクロの定義は、0、0L、0LL、あるいは((void*)0)に定義されていることが多いと思います。
0Lや0LLに定義されることがあるのは、次のような理由からです。

printf(“%p\n”, NULL);のような使い方をした場合、NULLがポインタと異なるサイズでは誤動作してしまいます。
たとえば、int型が16ビット、long型が32ビット、ポインタが32ビットの処理系を考えてみてください。
NULLが0に定義されていると、printfには16ビット分の実引数が渡されてしまいます。
ところが、printf内部ではポインタを期待しているので32ビット値を取り出そうとして破綻します。
これを回避するためにはNULLを0Lに定義しなければなりません。

ちなみに、C++では((void*)0)に定義されることはありません。
これは、Cではvoid*からint*など他のポインタへ暗黙的に型変換できるのに対し、C++では明示的なキャストが必要になるからです。
C++11以降では、NULLがnullptrに定義されていることもあるでしょう。

次に、0ですが、これはint型の値0に評価される整数定数です。
整数定数は右辺値ですので、それ自体のアドレスがどうとかいう話は間違っています。

最後に’\0’です。
これも間違いやすいのですが、0と同様、int型の値0に評価される整数定数です。
こちらもアドレス云々の話は間違っています。

C++の’\0’はchar型ですので、これと混同している人が非常に多いようです。
この話題に限った話ではありませんが、何の話をしているのかが不明瞭だと、そのような混乱が生じやすくなります。

‘\0’はint型の0ですが、あくまでも文字を表現するための表記です。
普通の整数値を表現するのに’\0’を使うことはあまりありません。

例外的に、char型に代入するときには、それが文字を意味していなくても’\0’を使うことはあるようです。
たとえば、struct lconvにあるchar型のメンバーに代入するときなどがそうですね。

今後も、似たような状況があれば、他サイトの情報に対しても突っ込みを入れる機会があるかもしれません。
以前はYahoo!知恵袋にも「ちょい足しアンサー」の機能があったのですが、今はなくなってしまったのでしかたありませんね。