Menu

C言語の文字列:char配列、ヌル終端文字、そして例

Cに文字列型はありません - 文字列とは '\0' バイトで終わる char の配列です。このページはその考え方を組み立て、続いてリテラル、表示、走査、そして = で文字列をコピーできない理由を扱います。

このページのコードはエディタで実行できます - 編集してすぐに結果を確認できます。

ほとんどの言語は自分の長さを知っている string 型を差し出します。Cはそうしません。Cにおける文字列は、1つの規則が付いた単なる char の配列です。テキストのあとに値ゼロのバイトが続き、それは '\0' と書かれヌル終端文字と呼ばれます。C言語の文字列に関する他のすべてが、このたった1つの約束事から導かれます。

文字列とは char 配列と '\0'

リテラルから char 配列を初期化して文字列を宣言します。

この配列は3バイトではなく4バイトに見えます。

name:  +-----+-----+-----+------+
       | 'A' | 'd' | 'a' | '\0' |
       +-----+-----+-----+------+
添字:     0     1     2     3

sizeof(name)4 です。コンパイラが文字を数え、終端文字のぶんを1つ足し、配列の大きさを決めてくれました。この余分な1バイトのせいで char name[3] = "Ada"; は起こるべくして起こるバグです - テキストは収まりますが終端文字が収まらないので、その先の何ものも文字列がどこで終わるか分からなくなります。

あとで中身を変える余地が欲しいときは、自分でサイズを与えて余裕を残しましょう。

char name[32] = "Ada";   /* 3文字、終端文字、そして28バイトの余り */

使われないバイトは初期化子によってゼロで埋められますが、これは無害です。

終端文字が重要な理由

Cのどの関数も長さを受け取りません。printf("%s", s) はアドレスを1つ渡され、'\0' に出会うまで前へ進みながらバイトを表示します。ですから終端文字のない char 配列は文字列ではなく、それを文字列として扱えば、たまたま後ろに続くメモリを読むことになります。

覚えるべき規則です。sizeof は記憶領域を、strlen はテキストを教えてくれます。 char buf[32] = "Ada" について sizeof buf32strlen(buf)3 です。両者は違う問いに答えており、それらを取り違えることはC言語で最もよくあるバグの1つです。

文字列リテラルと char 配列

この2行は似て見えますが、まったく違う振る舞いをします。

char  a[] = "hello";   /* 自分が所有する配列。リテラルから初期化される */
char *p   = "hello";   /* リテラルそのものを指すポインタ */

a はテキストの自分専用のコピーを保持する6バイトの配列です。変更できます。p はプログラムの読み取り専用の文字列データを指しています。そのリテラルは共有されており、p を通じた書き込みは未定義動作で、コンパイル時ではなく実行時にクラッシュするのが普通です。

ポインタがリテラルを指すときは必ず const char * と書きましょう。そうすればコンパイラがその書き込みをコンパイル時に拒否し、クラッシュになるのを防ぎます。この2つの選択は実のところポインタと配列についての問いで、ポインタと配列で扱います。

もう1つ知っておく価値のある違い。sizeof a6 ですが、sizeof p はテキストの長さに関係なくポインタの大きさ(多くのシステムで8)です。

文字列の表示と読み取り

%s は文字列を、%c は1文字を表示します。

入力には gets をまったく使わないでください - 安全に使う方法がないため言語から削除されました。バッファサイズを取りあふれさせない fgets を使いましょう。

fgets はあなたが押した改行を残しますが、それは文字列に欲しいものではまずありません。strcspn の行は最初の '\n' を見つけて終端文字で上書きします - それを取り除く簡潔で標準的な方法です。

文字列を1文字ずつ歩く

終わりが数えられるのではなく印されているので、慣用的なループは文字そのものを調べます。

条件 text[i] != '\0' はしばしば単に text[i] と短く書かれます。'\0' はゼロなので偽だからです。どちらも正しいのですが、学んでいる間は明示的な形のほうが読みやすいでしょう。

添字の代わりにポインタで歩くこともでき、これも同じくらい慣用的なCです。

toupper を呼ぶ前の unsigned char へのキャストは飾りではありません。<ctype.h> の関数は負の値に対して未定義であり、素の char は127を超えるバイトに対して負になりうるからです。

= で文字列をコピーすることはできない

これが新参者の誰もがぶつかる壁です。

char a[10];
a = "hello";        /* エラー: 配列型の式への代入 */

char b[10] = "hi";
char c[10];
c = b;              /* エラー - 同じ理由 */

配列名は書き換え可能な値ではないので、= にできることがありません。この省略記法が効く唯一の場所は宣言時の初期化であり、それはコンパイラが行う別の操作です。

実行時にコピーするには、<string.h>strcpy でバイトをコピーします。

同じ理屈が、== が文字列を比較しない理由も説明します。if (a == b) は2つのアドレスを比較するので、同一のテキストを保持する別々の配列に対して偽になります。代わりに strcmp(a, b) == 0 を使いましょう。これらの関数とより安全な親戚は文字列関数で扱います。

文字列の配列

文字列のリストは、ポインタの配列(読むだけの固定テキストの場合)か、2次元の char 配列(各項目が変更可能でなければならない場合)のどちらかです。

ポインタの配列は読み取り専用リテラルへの3つのアドレスを保持します - 小さいのですが、変更できません。char[3][16] の形は自分のメモリを48バイト確保するので、各行を書き換えられます。

よくある間違い

  • 終端文字の1バイトを忘れる。 char s[5] = "hello" には '\0' の余地がありません。strlen + 1 で大きさを取りましょう。
  • strlen のつもりで sizeof を使う。 ポインタに対する sizeof はテキストの長さではなくポインタの大きさを返します。
  • リテラルを指す char * を通じて書き込む。 そうしたポインタは const char * と宣言しましょう。
  • == で比較する。 それはアドレスの比較です。strcmp を使いましょう。
  • scanf("%s", buf) で読む。 サイズの上限がなく平然とあふれます。fgets を選びましょう。

よくある質問

C言語で文字列はどう宣言しますか?

char の配列としてです:char name[] = "Ada";。Cに組み込みの文字列型はありません - この配列は 'A''d''a' と、終わりを示す4番目の隠れたバイト '\0' を保持しています。あとでテキストを変える余地が欲しければ、配列に明示的なサイズを与えましょう:char name[32] = "Ada";

C言語のヌル終端文字とは何ですか?

文字列がどこで終わるかを示すバイト '\0'(値はゼロ)です。標準のどの関数も - printf("%s")strlenstrcpy - そのバイトが見つかるまで前へ読み進めます。それを持たない char 配列は文字列ではなく、それらの関数に渡すとメモリの末尾を越えて読むことになります。

なぜC言語では = で文字列を代入できないのですか?

配列は = でコピーできる値ではないからです。char a[10]; a = "hi"; はコンパイルできません。代わりに <string.h>strcpy(a, "hi") で文字をコピーするか、宣言時に初期化しましょう - char a[10] = "hi"; - これが省略記法の効く唯一の場所です。

C言語の char * と char [] の違いは何ですか?

char s[] = "hi" はテキストの自分専用のコピーを保持する書き換え可能な配列を作ります。char *p = "hi" は文字列リテラルへのポインタを作り、それは読み取り専用メモリにあります - 読むのは構いませんが、それを通じて書くとクラッシュします。変更するつもりなら配列を、読むだけならポインタを使いましょう。

Coddy programming languages illustration

Coddyでコードを学ぼう

始める