char s[6] = "hello"; で6個ぶん必要だった理由を明かします。文字列の最後には、必ずヌル文字 '\0' が置かれるからです。
s[0] s[1] s[2] s[3] s[4] s[5]
+-----+-----+-----+-----+-----+-----+
| 'h' | 'e' | 'l' | 'l' | 'o' |'\0' |
+-----+-----+-----+-----+-----+-----+
ここまでが文字列、という印'\0' は値が0の文字です。数字の '0'(値48)とは別物なので、混同しないでください。画面には何も表示されず、ただ「ここで終わり」を意味します。
なぜ印が必要なのか
配列を渡されただけでは、どこまでが文字列なのか誰にも分かりません。Cは配列の長さを覚えていないからです。そこで終わりに目印を置くという約束を作りました。printf の %s も strlen も、この印に出会うまで前から順に読み進めます。
#include <stdio.h>
#include <string.h>
int main(void)
{
char s[6] = "cat";
printf("%zu\n", sizeof(s)); // 6 ← 箱の数(確保した大きさ)
printf("%zu\n", strlen(s)); // 3 ← 文字数('\0' の手前まで)
return 0;
}sizeof と strlen の違いは頻出です。箱の数と中身の文字数は別、と覚えてください。char s[6] = "cat"; の場合、s[3] が '\0' で、s[4] と s[5] も 0 で埋められます。
下のビジュアライザで、1文字ずつ '\0' を探しに行く様子を見てみましょう。
印を失うとどうなるか
char s[5] = "hello"; // 5文字ぴったり。'\0' が入らない!
printf("%s\n", s); // どこまで読むか分からず暴走するこれはコンパイルが通ってしまいます。printf は '\0' を探して配列の外まで読み進め、たまたま見つかった 0 までを表示します。文字化けするか、境界外アクセスで異常終了するか、運が良ければ何事もなく動く——おなじみの未定義動作です。
罠・注意
- 必要な配列の大きさは「文字数 + 1」。この +1 を忘れるのが、Cの文字列で最も多い事故です。
'\0'と'0'と0と"0"はすべて別物です。'\0'は終端、'0'は文字のゼロ(値48)、0は数値、"0"は1文字の文字列(2バイト)。- 1文字ずつ手作業で配列に入れたときは、最後に自分で
s[n] = '\0';を書く必要があります。書き忘れると文字列として使えません。 strlenは呼ばれるたびに先頭から数え直します。ループの条件にi < strlen(s)と書くと、毎回数え直しが走って遅くなります。長さは先に変数へ入れておきましょう。- 配列の途中に
'\0'を書き込むと、そこが新しい終わりになります。後ろの文字は消えていませんが、文字列としては見えなくなる——この性質は t09-08 の定石で活用します。
まとめ: 文字列の最後には必ず '\0'。配列は「文字数+1」で確保し、sizeof(箱の数)と strlen(文字数)を区別する。