6日でできる 新C言語入門|C言語の文字列とNULL文字の基本

C言語の文字列は、char配列と終端の合図でできている。
NULL文字の役割を理解して、文字列を安全に扱えるようになろう。

C言語で文字列を扱うとき、最初に理解しておきたい大切な考え方があります。それは、C言語には文字列専用の特別な型があるわけではないということです。

多くのプログラミング言語では、文字列を1つのデータ型として自然に扱えることがあります。しかし、C言語では、文字列はchar型の配列として管理されます。

たとえば、Helloという文字列は、内部ではH、e、l、l、oという文字が1文字ずつ配列に格納されています。そして、その最後に、文字列の終わりを示すNULL文字である\0が入ります。

この\0があることで、C言語は「ここまでが文字列です」と判断できます。反対に、\0がないchar配列を文字列として扱おうとすると、どこまで表示すればよいのか分からなくなり、予期しない表示や不安定な動作につながることがあります。

つまり、C言語の文字列を理解するには、char配列とNULL文字の関係を押さえることがとても重要です。

この記事では、文字列とchar配列の関係、NULL文字の役割、文字列の初期化、printfによる表示、scanfによる入力、配列名とアドレスの関係、char型と文字コードの考え方まで、サンプルプログラムを使いながらやさしく解説していきます。

C言語の文字列はchar配列で管理される

C言語では、文字列はchar型の配列として扱います。char型は1文字を格納するための型です。

たとえば、次のように書くと、文字を1つだけ扱えます。

char ch = 'A';

一方、複数の文字を並べて文字列として扱いたい場合は、char型の配列を使います。

char word[] = "Hello";

この場合、word配列には次のような文字が入ります。

添え字中身
word[0]H
word[1]e
word[2]l
word[3]l
word[4]o
word[5]\0

見た目は5文字のHelloですが、配列の中では最後に\0が追加されます。そのため、必要な要素数は5個ではなく6個です。

この最後の\0をNULL文字、または終端文字と呼びます。

NULL文字は文字列の終わりを示す目印

C言語では、文字列の長さを別の場所で自動的に覚えているわけではありません。その代わりに、文字列の最後に\0を置いて、そこで文字列が終わることを表します。

たとえば、catという文字列は、内部では次のように格納されます。

添え字0123
中身cat\0

printfで%sを使って表示すると、配列の先頭から順番に文字を読み、\0が見つかったところで表示を止めます。

つまり、\0は画面に表示するための文字ではなく、ここで文字列が終わるという合図です。

図:文字列はchar配列とNULL文字でできている

この図から分かること

この図から分かるのは、C言語の文字列は文字だけでなく、最後にNULL文字である\0を持っているということです。

Helloは5文字ですが、C言語の文字列として扱うには、最後に\0を入れるため、少なくとも6つの要素が必要です。printfで%sを使うと、この\0を見つけるまで文字を表示します。

char配列と文字列の違い

char型の配列だからといって、必ず文字列として安全に扱えるわけではありません。

大切なのは、最後に\0が入っているかどうかです。

書き方内容文字列として扱えるか
char a[3] = {'C', 'A', 'T'};文字が3つ並んだ配列安全とはいえない
char b[4] = {'C', 'A', 'T', '\0'};終端文字付きのchar配列文字列として扱える
char c[] = "CAT";自動で\0が付く文字列として扱える

1文字ずつ初期化する場合、文字列として使うなら\0を自分で入れる必要があります。

一方、ダブルクォートを使って文字列リテラルとして書いた場合は、C言語が自動で最後に\0を追加します。

char c[] = "CAT";

この書き方では、見た目は3文字ですが、実際の配列要素はC、A、T、\0の4つになります。

文字列の定義と表示を確認する

次のプログラムでは、終端文字があるchar配列と、文字列リテラルで初期化したchar配列を表示します。

プロジェクト/ファイル名: Lesson34_1/main.c

#include <stdio.h>

int main(void) {
    char word1[4] = { 'C', 'a', 't', '\0' };
    char word2[] = "Dog";
    char letters[3] = { 'B', 'o', 'x' };

    // word1は最後にNULL文字があるため、文字列として表示できる
    printf("word1: %s\n", word1);

    // word2は文字列リテラルで初期化しているため、自動でNULL文字が付く
    printf("word2: %s\n", word2);

    // lettersにはNULL文字がないため、文字列として表示するのは安全ではない
    // printf("letters: %s\n", letters);

    return 0;
}

実行結果

word1: Cat
word2: Dog

このプログラムでは、word1とword2は文字列として表示できます。

word1は、1文字ずつ初期化していますが、最後に'\0'を入れています。そのため、printfの%sで表示できます。

word2は、"Dog"という文字列リテラルで初期化しています。この場合、C言語が自動的に最後へ\0を追加します。そのため、文字列として安全に扱えます。

一方、lettersはB、o、xの3文字だけを持つchar配列です。最後に\0がないため、printfの%sで表示するのは安全ではありません。文字列の終わりが分からず、配列の外側まで読み続けてしまう可能性があります。

文字列リテラルで初期化するとNULL文字が自動で入る

C言語では、ダブルクォートを使って文字列を初期化すると、最後にNULL文字が自動で追加されます。

char name[] = "Taro";

この場合、name配列の中身は次のようになります。

添え字01234
中身Taro\0

Taroは4文字ですが、配列には5個分の要素が必要です。

明示的に要素数を書く場合は、NULL文字の分も含めて考えます。

char name[5] = "Taro";

これは問題ありません。T、a、r、o、\0の5つが入るからです。

しかし、次のように書くと、NULL文字のための場所が足りません。

char name[4] = "Taro";

このような書き方は避けるべきです。文字列として扱うには、文字数 + 1 の領域を用意する必要があります。

文字列入力にはchar配列を用意する

キーボードから文字列を入力する場合は、入力された文字を格納するためのchar配列を用意します。

代表的には、scanfと%sを使って入力できます。

char name[16];

scanf("%15s", name);

ここでは、name配列を16文字分用意しています。入力では最大15文字まで読み込むようにしています。残りの1文字分はNULL文字のための領域です。

文字列入力では、配列の大きさを意識することが大切です。入力された文字列の最後には\0が必要になるため、配列のサイズいっぱいまで文字を入れてしまうと危険です。

キーボードから文字列を入力する

次のプログラムでは、ユーザーから名前を入力して、その内容を表示します。

プロジェクト/ファイル名: Lesson34_2/main.c

#include <stdio.h>

int main(void) {
    char nickname[16];

    printf("ニックネームを入力してください: ");

    // 最大15文字まで読み込む。残り1文字分はNULL文字のために使う
    scanf("%15s", nickname);

    printf("入力されたニックネーム: %s\n", nickname);

    return 0;
}

実行結果

ニックネームを入力してください: Sora
入力されたニックネーム: Sora

このプログラムでは、nicknameというchar配列を用意しています。

char nickname[16];

この配列には、最大で15文字分の入力と、最後のNULL文字を入れられます。

scanfでは、次のように書いています。

scanf("%15s", nickname);

%15sと指定することで、最大15文字まで読み込むようにしています。これにより、配列の範囲を超えて入力される危険を減らせます。

Visual Studioでscanfを使う場合、環境設定によって警告が表示されることがあります。学習用として動作確認する場合は、必要に応じてSDLチェックの設定も確認しておきましょう。

文字列入力で&が不要な理由

数値をscanfで入力するときは、変数の前に&を付けます。

int age;

scanf("%d", &age);

これは、scanfが入力された値をageの場所へ書き込むためです。そのため、ageのアドレスを渡す必要があります。

一方、文字列を入力するときは、配列名をそのまま渡します。

char name[16];

scanf("%15s", name);

このとき、nameの前に&は付けません。

理由は、配列名nameが、配列の先頭アドレスを表すからです。scanfは、nameが示す場所から順番に文字を書き込んでいき、最後に\0を付けます。

入力するものアドレス指定
int型の変数scanf("%d", &age);&ageを使う
double型の変数scanf("%lf", &height);&heightを使う
char配列の文字列scanf("%15s", name);配列名をそのまま使う

文字列入力では、配列名が先頭アドレスを表すため、&を付けないと覚えておきましょう。

図:文字列入力で配列名をそのまま渡す理由

この図から分かること

この図から分かるのは、scanfで数値を入力するときと文字列を入力するときでは、渡すものが違うという点です。

int型の変数に入力する場合は、変数の場所を示すために&ageを渡します。一方、char配列へ文字列を入力する場合は、配列名nameが配列の先頭アドレスを表すため、そのまま渡します。

文字列入力では、入力された文字が配列の先頭から順番に入り、最後に\0が追加されます。

printfはNULL文字までを文字列として表示する

printfで%sを使うと、char配列の先頭から文字を順番に表示します。そして、\0が見つかったところで表示を止めます。

printf("%s\n", name);

このとき、printfは配列のサイズを見ているわけではありません。どこまでが文字列なのかは、\0によって判断しています。

そのため、NULL文字がないchar配列を%sで表示しようとすると、表示が止まる位置が分からなくなります。結果として、配列の範囲外まで読んでしまう可能性があります。

文字列として表示するchar配列には、必ず\0が必要です。

NULL文字と空文字列

NULL文字は、文字列の終わりを示す文字です。値としては0です。

また、文字が1つもない文字列を空文字列と呼びます。

char empty[] = "";

この場合、empty配列の中には、最初から\0だけが入ります。

添え字0
中身\0

空文字列は、長さ0の文字列です。何も表示されませんが、文字列としては正しく終端されています。

つまり、C言語では、文字が何もない場合でも、文字列として扱うには\0が必要です。

char型は文字を表すが実体は整数値

char型は文字を扱う型ですが、コンピュータの内部では整数値として管理されています。

たとえば、ASCIIコードでは、Aは65、aは97、\0は0です。

次のプログラムで、文字と文字コードの関係を確認してみましょう。

プロジェクト/ファイル名: Lesson34_3/main.c

#include <stdio.h>

int main(void) {
    char letter = 'B';
    char end = '\0';

    // 文字として表示する
    printf("文字: %c\n", letter);

    // 整数として表示すると、文字コードが確認できる
    printf("文字コード: %d\n", letter);

    // NULL文字は整数値としては0
    printf("NULL文字の値: %d\n", end);

    return 0;
}

実行結果

文字: B
文字コード: 66
NULL文字の値: 0

このプログラムでは、letterにBを代入しています。printfで%cを使うと文字として表示され、%dを使うと整数値として表示されます。

BのASCIIコードは66です。また、NULL文字である\0は、整数値としては0です。

このことから、char型は文字を扱うための型でありながら、内部的には数値としても扱えることが分かります。

図:NULL文字は値0の終端文字

この図から分かること

この図から分かるのは、NULL文字である\0は文字列の終わりを示す特別な文字であり、値としては0であるということです。

C言語では、文字列を表示するときに\0を見つけるまで文字を読み取ります。また、char型の文字は内部では文字コードとして扱われるため、Aやaのような文字にも数値が対応しています。

文字列を扱うときの注意点

C言語の文字列は、char配列とNULL文字で成り立っています。そのため、扱うときにはいくつか注意が必要です。

NULL文字の分の領域を確保する

Helloを入れるなら、5文字分だけでなく、\0を含めて6文字分の領域が必要です。

char text[6] = "Hello";

文字列リテラルなら自動でNULL文字が付く

char text[] = "Hello";

この場合、C言語が自動的に\0を追加します。

1文字ずつ初期化するならNULL文字を忘れない

char text[6] = { 'H', 'e', 'l', 'l', 'o', '\0' };

文字列として扱う場合は、最後に\0が必要です。

scanfで文字列を入力するときは配列サイズに注意する

char name[16];

scanf("%15s", name);

このように、配列の大きさより1小さい文字数を指定しておくと、NULL文字のための領域を残せます。

空白を含む文字列には注意する

scanfの%sは、空白で入力を区切ります。たとえば、Taro Yamadaと入力すると、Taroまでしか読み込まれません。

空白を含む文字列を扱う場合は、別の入力方法を学ぶ必要があります。まずは、scanfの%sは空白を含まない単語の入力に向いていると覚えておくとよいです。

文字列と配列の関係を理解するとC言語らしさが見えてくる

C言語の文字列は、char型の配列とNULL文字で成り立っています。文字列専用の特別な型ではなく、文字を1つずつ配列に並べ、最後に\0を置くことで文字列として扱います。

ダブルクォートで文字列を初期化すると、最後に\0が自動で追加されます。一方、1文字ずつchar配列を初期化する場合は、文字列として扱うために自分で\0を入れる必要があります。

また、scanfで文字列を入力するときは、配列名をそのまま渡します。これは、配列名が配列の先頭アドレスを表すためです。数値入力で&を使う場合との違いも、文字列を理解するうえで大切なポイントです。

C言語の文字列は少し低レベルな仕組みに見えるかもしれませんが、その分、メモリ上で文字がどのように並んでいるのかを学ぶよい題材になります。char配列、添え字、NULL文字の関係を丁寧に押さえながら、文字列の扱いに慣れていきましょう。