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

配列であるがゆえの危うさ

Cでは文字列は単にchar型の配列であって、それを終端のNULLを目安にして文字の連続したものと見なしているだけです。さらにCでは、コンパイル時に配列の要素数をチェックしないため、配列の長さを無視することも簡単にできてしまいます。配列の要素数は、それを宣言したプログラマーが意識しなければなりません。

文字数のオーバーに注意

例えば、以下のように要素数7(6+1)のchar型配列を宣言し、strcpy関数でそれ以上の長さの文字列定数をコピーするソースを記述しても、多くの処理系ではコンパイル時にエラーとはなりません。
char message[6 + 1];
strcpy(message, "Hello world!");

しかし、上のようなソースをコンパイルして実行すると、WindowsやLinuxなどメモリ管理のしっかりしたOSでは「メモリを不正にアクセスした」という内容の例外が発生してプログラムが強制終了されます。MS-DOSのようにメモリ管理のアバウトなOSでは、特に問題なく処理が実行されますが、途中で何かとんでもない動作をすることがあります。場合によってはプログラムが暴走することもあるでしょう。

こういった場合、メモリの中は図4のようになっています。つまり、本来なら他の変数が使用するはずの領域に、勝手に文字列のデータが書き込まれているのです ※3

他の目的に使われるメモリ領域に値を書き込んでしまうことを「領空侵犯」などと呼んでいます。人によっては「領海侵犯」とも言うようです。空か海かというのは、趣味の問題だと思います

要素数を意識する

このように、他の変数領域に勝手にデータを書き込む「領海侵犯事件」が簡単に起きてしまうところが、Cの危ういところでもあります。文字列を扱う場合には、確保した要素数をしっかり踏まえていなければなりません。

なぜCでは文字列の長さを無視できてしまうのかと言えば、その第1の理由は文字列が単なる配列でしかないからです。先述したように、Cには複数の文字の連続を1つの型として扱う機能がありません ※4

strcpyなど文字列を操作する関数は、引数として文字列の先頭アドレスを受け取り、その内部ではポインタを使って処理を行います。ポインタは配列の各要素を間接的に示すものであって、配列の要素数をチェックする機能を持っていません。そのため、コピー元の文字列がコピー先配列の要素数を超える長さでも、そのことを関知しないで処理してしまうのです。

なお、ポインタに関しては、回を追って詳しく紹介します。

Cをベースにして仕様を拡張されたオブジェクト指向言語のC++には、文字列を扱うためのクラス"string"が用意されています。C++のstringクラスでは、Cの弱点でもありまた特徴でもある「単なるchar型の配列」という部分を覆い隠し、配列に対する不用意なアクセスを防ぐようになっています