ヌル文字
A | B | C | \0 |
空文字列とは先頭の文字がいきなりヌル文字で始まる文字列のことである。
\0 |
よって、次のように配列の構成要素すべてがヌル文字である必要はない。
\0 | \0 | \0 | \0 |
strcpy(str, "");
このコードは、空文字列、即ちヌル文字だけで構成される文字列をstrにコピーする。つまり、str[0]に'\0'が格納されるので、配列strは空文字列になり、クリアに成功している。
また、次のようなコードでも問題ない。
str[0] = '\0';
一般的には前者のコードが使われることが多いのが、後者のコードの方が優れている。なぜならば単純な代入であり、関数呼び出しのオーバーヘッドがない。つまり高速で処理できるからである。
[補講]前者のコードの場合、2つの引数があるので2つの引数をスタックにPUSHされる。例えばポインタが4バイトであれば、引数だけで8バイトの領域がスタックに積まれることになるのである。さらに、strcpy関数内でループ処理が実行される("\0"だけをコピーなので実質的にループされずに1回のコピーで終わる)。 ◇
まず次のようなコードを考える。
strcpy(str, NULL);
このコードでは配列strをクリアすることはできない。なぜならばNULLは0番地を指している。strcpyは内部の処理として、ヌル文字が現れるまでコピーし続ける。よって、このコードを実行すると0番地を先頭とする文字列がコピーされる。
たまたま0番地にヌル文字が入っていればstrはクリアされるが、一般にはゴミが入っていると考えられるのでstrはクリアされるどころかヌル文字が登場するまでの大量のゴミが格納されてしまうわけである。strの宣言によっては配列の要素数を超えてしまいインデックスエラーになることもありうるだろう。
本来NULL(ヌルポインタ)とはどんなオブジェクトも指していないことを表す、無効なポインタである。よって、NULLの指す実体のない領域にアクセスしてはならないのである。
次は次のようなコードを考えてみる。
strcpy(str, '\0')
C言語では単一引用符で囲んで文字定数('x'など)はint型を持つ*1ので、'\0'は整数定数0と同じである。よって、このコードはstrcpy(str, 0)と同一になる。
もし関数プロトタイプが与えられていれば、引数である整数0はポインタに変換される。よって、整数0をvoidポインタにキャストするとNULL(ヌルポインタになる。つまり、strcpy(str, NULL)と同じ意味になる。
一方関数プロトタイプが与えられていなければ、第1引数であるstrをポインタとして、第2引数の'\0'をint型の引数として、strcpyに対して渡される。しかし、strcpy側は両方の引数をポインタとして受け取る。ここでポインタと整数の大きさが同じかどうかによって動作が異なる。
このとき、整数とポインタの大きさが異なる場合、即ちsizeof(int)≠sizeof(void*)の場合、プログラムとして正しくないためエラーになる。
もし整数とポインタの大きさが同じ場合、即ちsizeof(int)=sizeof(void*)の場合、整数とヌルポインタの内部表現*2が同じであればstrcpy(str, NULL)と同じ意味になる。整数とヌルポインタの内部表現が異なればstrcpyは予想していない領域からコピーしてきてしまう。
文字列をクリアするときはstrcpy(str, "")で十分だが、何らかの理由により配列strの領域をすべてヌル文字でクリアしたい場合はmemset(str, '\0', sizeof(str))を実行すればよい。これにより、第1引数の指すポインタの位置&str[0]から、第3引数のsizeof(str)のバイト数だけ、第2引数の文字'\0'で埋め尽くされる。
例:文字列リテラルの例