C/C++:数値の0NULL,空文字('\0'),空文字列("")の違い

C/C++の数値の0NULL,空文字('\0'),空文字列("")の違いがよくわからなくなったので整理する。

内部的な値

まず,これらの内部的な値を以下のプログラムで確認する。

null.c
/// \file      null.c
#include <stdio.h>
#define PRINT(x) printf(#x":%x\n", x);

int main(void) {
  PRINT(0);
PRINT(NULL); PRINT('\0'); PRINT(""); PRINT(""[0]);
printf("size of \"\":%d\n", sizeof("")/sizeof("")); return 0; }

上記プログラムをUbuntu 16.04でgcc 5.4.0でコンパイルして実行すると以下の結果が出力される(コンパイラーが警告を出すが今回は無視)。

null.cの実行結果
0:0
NULL:0 '\0':0 "":400606 ""[0]:0
size of "":1

この結果を踏まえて,それぞれの意味を整理する。

数値の0NULL,空文字('\0'),空文字列("")の違い
項目 説明
0 整数の0。
NULL どこも参照していないことを表すポインター(マクロ)。値はアドレスの0番(値0)。
空文字('\0' ASCIIコード(文字コード)値0(NULL文字)を指す。
空文字列("" 文字'\0'の配列(文字配列)。値はこの配列の先頭アドレスとなる。

整数の0NULL,空文字('\0')は値がともに0であるため,結果としては混同して使っても問題ないことが多い。

ただし,空文字列は大きく意味が異なる。空文字列は空文字('\0')を要素として持つ要素数1の配列である。そのため,単独の空文字列の値は配列の先頭アドレスとなる。空文字列の先頭要素[0]は空文字('\0')であるため値は0である。

間違いの例

例えば,引数に文字列を受け取り,受け取った文字列を1文字ずつ処理することを考える。このときに,文字列を1個ずつループするために,以下のようなループで処理する。

PrintChars.c
/// \file      PrintChars.c
#include <stdio.h>

void PrintChars(const char* pIn) {
  // for (; *pIn; ++pIn) { // OK
  for (; pIn; ++pIn) { // NG
    printf("%c\n", *pIn);
  }
}

int main(void) {
  PrintChars("ab");
  return 0;
}

ここでは,文字列の終端のNULL文字('\0')に到達することをループの終了条件に指定している。

しかし,このPrintChars.cをコンパイルして実行すると無限ループが発生する。理由は6行目のfor文の評価式(pIn)をポインター(文字配列)のまま評価してしまっているからだ。この場合,アドレスとしてNULL文字の位置まで到達しても,アドレスのまま評価してしまっているため,終了しなくなってしまっている。

正しくは,5行目のコメントに書いてあるように,評価式に関節参照演算子*を使用して,文字配列のアドレスではなく,文字配列の要素を参照(*pIn)する。こうすることで,文字列の終端に到達したときは空文字('\0')が評価され,期待通りに以下の結果が得られる。

PrintChars.cの実行結果
a
b

まとめ

おそらく,C/C++の中では初歩的なことだとは思うが,自分の中で理解が不十分であり,ネット上にわかりやすい情報がなかったので整理した。次回からは同じような間違いや勘違いは減るだろう。

nullと””、\0とEOFの違いについて。 -いつも気になっていたのですが、(- C言語・C++・C# | 教えて!goo

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です