第23回
データ構造(2)~文字列という特殊な配列

宣言後に初期化を

文字列を扱う配列を宣言しただけの状態では、中身の各要素に何が入っているかは不定です。そのため、文字列変数を宣言した後には必ず、何か文字列をコピーしなければなりません。

ゴミが残っている

例えば、
char message[16 + 1];
のように宣言した場合、この段階では内容は初期化されません。プログラマーが各要素に対して明確に値を代入する行為──初期化をしなければなりません。

初期化される前の不定な値を、俗に「ゴミ」と呼びます。直前に実行された処理で確保された変数の値など、現在実行している処理ではまったく意味を持たない値が入ったままになっているのです。

場合によっては、その領域が運良く未使用で「0」(数値のゼロ)が並んでいる場合もありますが、それも確実ではありません。

ゴミにアクセスできてしまう

以下のように、要素17個の配列に要素7個(文字6バイト+NULL1バイト)の文字列をコピーした場合、文字列としては最後の'!'の次にNULLが置かれてそこで終端となりますが、配列としてはその後ろにも要素は存在しています。

char message[16 + 1];
strcpy(message, "Hello!");
例えばこのとき
char c;
c = message[4];
とすれば、変数cには(先頭の添字が0から始まるため)5番目の要素である'o'が代入されます。さらに
c = message[8];
とした場合、終端のNULLを通り越して9番目の要素がcに代入されてしまいます。もちろん、その値が何であるかは不定です。文字として扱えない値が入っている可能性もあります。

ゴミをアクセスするプログラム

リスト1とリスト2は、上述のようなCの文字列のクセを試すためのプログラムのソースです。リスト1は、宣言しただけで初期化していないchar型配列を文字列として標準出力に送る――という内容、リスト2は初期化された文字列終端のNULLより後ろにある配列の要素を1文字と見なして標準出力に送る――という内容です。

リスト1では、コンパイル時に「変数が初期化されていない」といった意味の警告が出ますが、コンパイルされて実行形式ファイルが生成されます。実行すると、ディスプレイに意味不明の文字列が表示されるはずです。先述したように、運良く何か文字列が表示されて無事終了するかもしれませんし、場合によっては画面いっぱいにたくさんの記号や文字が流れ出し、例外が発生して強制終了となるかもしれません。

リスト2は、警告も出ずにコンパイルが終了して実行形式ファイルが生成されます。終端のNULLより後ろにある要素を表示するという処理は、文字列の扱いとしては間違っていますが、配列の扱いとしては何の問題もありません。そのため、エラーにも警告にもならないのです。しかし、実行すると「ゴミ」が1文字標準出力に送られます。これもまた、文字として表示できる値かどうかは分かりません。

わざわざ試す必要はないので、実行形式ファイルは提供しないことにしておきます。試しにコンパイルして実行しても構いませんが、その結果に責任は負えません。もちろん、ディスクやメモリを壊すようなことはありませんが、OSが不安定になる場合があるかもしれません(その場合は再起動してください)。実行には、くれぐれもご注意を。

リスト1:初期化していない文字列の要素を無理矢理表示する
#include <stdio.h>

int main(void)
{
  char message[16 + 1];

  puts(message);
  return (0);
}

リスト2:文字列終端のNULLより後ろの要素を無理矢理表示する
#include <stdio.h>
#include <string.h>    /*  */

int main(void)
{
	char message[16 + 1];
	char c;

    (void)strcpy(message, "Hello!");
    c = message[8];
	putchar(c);
	return (0);
}