
C言語基礎|ナル文字を持たない配列
その配列、本当に文字列? ナル文字がないだけで、扱いは別モノになる!
ナル文字がないと「文字列」としては終われない
C言語の文字列は、見た目が文字の並びでも 末尾にナル文字(値が0の文字) があることで「ここまでが文字列です」と分かる仕組みでした。
でも、配列のサイズがギリギリで ナル文字が入らない と、そこには「終わりの合図」がありません。
この状態は《文字列》ではなく、《文字が並んだ配列》として扱う必要があります。

まず結論:文字列と、文字配列の違い
文字列 vs ナル文字を持たない配列
| 種類 | 末尾のナル文字 | 表示方法 | 長さの求め方 | ありがちな事故 |
|---|---|---|---|---|
| 文字列 | ある | printf の %s でそのまま表示できる | '\0' まで数える | 安全に扱いやすい |
| ナル文字を持たない配列 | ない | 1文字ずつ、要素数ぶん表示する | 要素数で管理する | %s で表示すると暴走しやすい |
表の説明
- 文字列は「終端が分かる」ので、%s や puts で扱えます
- ナル文字がない配列は「終端が分からない」ので、配列の要素数が必須になります
なぜ char str[4] = "ABCD"; は危険なの?
次の宣言を見てみましょう。
char str[4] = "ABCD";
"ABCD" は見た目4文字ですが、文字列リテラルは末尾にナル文字が付きます。つまり必要なのは 5文字分 です。
でも配列は4文字分しかないので、末尾のナル文字が入りません。
その結果、この宣言は次と同じ扱いになります。
char str[4] = {'A', 'B', 'C', 'D'};
メモリ上のイメージ

説明
- 文字列なら最後に 0 が入って終端になります
- この配列は 0 が無いので、「どこまで表示してよいか」が分かりません
サンプルプログラム
ナル文字を持たない配列を安全に表示するプログラム例です。
プロジェクト名:chap9-11-1 ソースファイル名:chap9-11-1.c
// ナル文字を持たない配列を安全に表示する例
#include <stdio.h>
// 配列 a を n 個ぶん表示(文字列としては扱わない)
void put_chars(const char a[], int n)
{
for (int i = 0; i < n; i++)
putchar(a[i]);
}
int main(void)
{
char code[4] = "ABCD"; // 終端のナル文字は入らない
printf("これは文字列ではなく、4文字の配列です:");
put_chars(code, 4);
putchar('\n');
return 0;
}実行例
これは文字列ではなく、4文字の配列です:ABCDよくある事故:%s で表示すると何が起きる?
もし次のように書いたらどうなるでしょう。
printf("%s\n", code);
これは 文字列として終端の 0 を探しにいく 表示です。
ところが code には終端がないので、メモリ上でたまたま 0 が出てくるまで読み続けます。
暴走のイメージ

図の説明
- 配列の外まで読んでしまい、意味不明な文字が出たり、クラッシュしたりします。
- これは「終端を持たない配列」を「文字列」として扱ったのが原因です。」
文字列の配列を 1文字ずつ走査する話(2次元配列の復習も兼ねる)
文字列の配列(2次元配列)を 各文字を1文字ずつ走査して表示 します。
これは「文字列の終端ナル文字まで読む」考え方の練習としてとても良いです。
プロジェクト名:chap9-11-2 ソースファイル名:chap9-11-2.c
// 文字列の配列を 1文字ずつ走査して表示(別メッセージ)
#include <stdio.h>
void put_strary_bychar(const char s[][6], int n)
{
for (int i = 0; i < n; i++) {
int j = 0;
printf("項目%d:", i);
while (s[i][j]) {
putchar(s[i][j]);
j++;
}
putchar('\n');
}
}
int main(void)
{
char words[][6] = {"Turbo", "NA", "DOHC"};
puts("1文字ずつ順番に表示します。");
put_strary_bychar(words, 3);
return 0;
}実行例
1文字ずつ順番に表示します。
項目0:Turbo
項目1:NA
項目2:DOHC添字が1つ増えると何が変わる?
1次元の走査と2次元の走査(イメージ)
1次元(文字列):
s[i] を i で進める
2次元(文字列の配列):
s[i][j]
i: 何番目の文字列か
j: その文字列の何文字目か
説明
- 文字列は char の並びなので添字は1つ(i)で走査できます。
- 文字列の配列は「文字列が複数ある」ので、行(i)と列(j)の2つの添字でアクセスします。
命令の書式と「何をする命令か」
for の書式と役割
- 書式
for (初期化; 継続条件; 更新) 文; - 何をする?
回数が決まっている繰り返しに向いています。
今回は「n個の要素を表示する」「n個の文字列を表示する」で使っています。
while の書式と役割
- 書式
while (条件式) 文; - 何をする?
条件式が真の間、繰り返します。
文字列走査では s[i][j] が 0 になるまで進めるのが定番です。
putchar の書式と役割
- 書式
int putchar(int c); - 何をする?
1文字を標準出力に出します。戻り値は出力した文字(エラー時は EOF)です。
文字列を1文字ずつ表示したいときに活躍します。
printf の書式と役割(注意つき)
- 書式
int printf(const char *format, ...); - 何をする?
書式付きで表示します。%s は「ナル文字で終わる文字列」を表示します。
だから、ナル文字がない配列に %s を使うのは危険です。
演習問題
演習9-11:$$$$$ で入力終了する「文字列の配列」読み込み&表示
文字列の個数と最大長をオブジェクト形式マクロで定義する。
文字列を読み込み、$$$$$ が入力されたら終了する。
$$$$$ より前に入力された文字列だけ表示する。
解答例
プロジェクト名:chap9-11-3 ソースファイル名:chap9-11-3.c
Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。
#include <stdio.h>
#include <string.h>
#define MAX_N 5
#define MAX_LEN 128
int read_strings(char s[][MAX_LEN], int n)
{
int count = 0;
for (int i = 0; i < n; i++) {
printf("入力%d:", i);
scanf("%127s", s[i]);
if (strcmp(s[i], "$$$$$") == 0)
break;
count++;
}
return count;
}
void print_strings(const char s[][MAX_LEN], int n)
{
for (int i = 0; i < n; i++)
printf("s[%d] = %s\n", i, s[i]);
}
int main(void)
{
char s[MAX_N][MAX_LEN];
puts("$$$$$ で入力終了です。");
int used = read_strings(s, MAX_N);
puts("入力された文字列一覧:");
print_strings(s, used);
return 0;
}解説
- MAX_N と MAX_LEN をマクロにすると、配列サイズ変更が簡単になります。
- read_strings は「何個入力できたか」を返すのがコツです。
- strcmp を使って $$$$$ かどうかを判定します。
演習9-12:文字列の配列に入った各文字列を反転する
void rev_strings(char s[][128], int n);
を作成し、各文字列をその場で反転させる。
解答例
プロジェクト名:chap9-11-4 ソースファイル名:chap9-11-4.c
#include <stdio.h>
int str_length(const char s[])
{
int len = 0;
while (s[len])
len++;
return len;
}
void rev_string(char s[])
{
int left = 0;
int right = str_length(s) - 1;
while (left < right) {
char tmp = s[left];
s[left] = s[right];
s[right] = tmp;
left++;
right--;
}
}
void rev_strings(char s[][128], int n)
{
for (int i = 0; i < n; i++)
rev_string(s[i]);
}
int main(void)
{
char s[2][128] = {"SEC", "ABC"};
rev_strings(s, 2);
printf("%s\n", s[0]);
printf("%s\n", s[1]);
return 0;
}解説
- 反転は左右から交換していくのが定番です。
- rev_strings は「各行の文字列 s[i] を rev_string に渡す」だけでOKです。
- ここでも文字列なので、終端ナル文字までが対象になります。
