このページをはてなブックマークに追加このページを含むはてなブックマーク このページをlivedoor クリップに追加このページを含むlivedoor クリップ

目次

文字列

  • C言語における文字列とは、最後がヌル文字('\0')で終わる文字の並びである。よって、文字列のコピーをするときは、ヌル文字も含めてコピーしなければならない。

ヌル文字

ヌル文字

  • 値0を持つ文字のこと。
    • 内部表現において、すべてのビットは0である。
  • エスケープシーケンスを用いてヌル文字を表すと'\0'である。
  • 一般的に文字列の終端を表すために使われる。

文字列と空文字列

文字列「ABC」

ABC\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'で埋め尽くされる。

文字列リテラル

  • 2重引用符(")で囲まれた(0個以上の)文字の並びである。
    • 終わりには自動的にヌル文字が付加される。
  • 「リテラル」という言葉にはヌル文字を含むか否かにかかわらずに、最後まで字句通りに扱うというイメージになる。
  • 文字列リテラルは定数とは限らない。
    • 同じつづりの文字列リテラルを区別する処理系も存在する。つまり、同一の文字列リテラルが、同一のメモリを共有することを前提するとプログラムを書いてはならない。
    • さらに、プログラムの可搬性を考えれば、文字列リテラルの書き換えが可能であることを仮定してはならない。

例:文字列リテラルの例

  • "ABCDEF"
    • メモリ上では「'A', 'B', 'C', 'D', 'E', 'F', '\0'」と表される。これは文字列でもある。
    • よって、"ABCDEF"は文字列かつ文字列リテラルである。
  • "ABC\0DEF"
    • メモリ上では「'A', 'B', 'C', '\0', 'D', 'E', 'F', '\0'」と表される。これは文字列ではない。
    • よって、"ABC\0DEF"は文字列でない文字列リテラルである。


*1 K&R時代のC言語では処理のほとんどでint型が使われていた。計算を行うときや関数の引数として渡すときは、char型はすべてint型に昇格されていた。このような背景により、C言語での文字定数はint型なのである。一方、C++ではC言語よりも厳密に型を区別するので、文字定数はchar型である
*2 整数0のビット構成は全ビットが0であることが保障される。しかし、ヌルポインタは全ビットが0であるとは限らない。