C言語で文字列を扱う方法には、ダブルクォートで書く文字列リテラルと、メモリ上に自前で領域を持つchar配列の2つがあります。
見た目は似ていますが、書き換え可否・サイズの扱い・有効期間が大きく異なります。
本記事では、その違いを初心者の方にも分かりやすく、具体例とコードで丁寧に解説します。
文字列リテラルとchar配列の基本
文字列リテラルとは
文字列リテラルは、"Hello"
のようにダブルクォートで書く文字の並びです。
プログラムの実行中ずっと存在する静的領域に配置され、末尾には必ずヌル文字'\0'
が付加されます。
Cでは歴史的経緯により型はchar[N]
ですが、書き換えは未定義動作です。
実務では、const char *
で指すのが定石です。
#include <stdio.h>
int main(void) {
// 文字列リテラルを指すポインタ。constを付けるのが慣習で安全です。
const char *msg = "Hello, Literal";
printf("%s\n", msg); // そのまま読み取り専用で使える
return 0;
}
Hello, Literal
リテラルは読み取り専用として扱うのが最重要ポイントです。
これは、多くの処理系でリテラルが読み取り専用領域に置かれるためです。
char配列とは
char配列は、char s[] = "ABC"
のように自分の領域に文字を格納する入れ物です。
配列は自前のメモリを持ち、内容を書き換えられるのが特徴です。
関数の中で宣言すれば自動記憶域期間(いわゆるスタック)になり、スコープから出ると消えます。
#include <stdio.h>
int main(void) {
// リテラルの「コピー」を持つ配列。末尾の'#include <stdio.h>
int main(void) {
// リテラルの「コピー」を持つ配列。末尾の'\0'も自動で入ります。
char s[] = "ABC"; // サイズは4文字分('A','B','C','\0')
s[0] = 'a'; // 配列は書き換え可
printf("%s\n", s); // aBC と表示
return 0;
}
'も自動で入ります。
char s[] = "ABC"; // サイズは4文字分('A','B','C','#include <stdio.h>
int main(void) {
// リテラルの「コピー」を持つ配列。末尾の'\0'も自動で入ります。
char s[] = "ABC"; // サイズは4文字分('A','B','C','\0')
s[0] = 'a'; // 配列は書き換え可
printf("%s\n", s); // aBC と表示
return 0;
}
')
s[0] = 'a'; // 配列は書き換え可
printf("%s\n", s); // aBC と表示
return 0;
}
aBC
配列は「実体」を持ち書き換え可能であることが、リテラルとの最初の違いです。
文字列は’\0’で終わる
Cの「文字列」は必ず'\0'
で終端します。
"ABC"
というリテラルには自動で'\0'
が付き、char s[] = "ABC"
も同様です。
配列に手で文字を入れる場合は、自分で終端を書きます。
#include <stdio.h>
int main(void) {
char t[4];
t[0] = 'A';
t[1] = 'B';
t[2] = 'C';
t[3] = '#include <stdio.h>
int main(void) {
char t[4];
t[0] = 'A';
t[1] = 'B';
t[2] = 'C';
t[3] = '\0'; // これを忘れると「文字列」になりません
printf("%s\n", t); // ABC
return 0;
}
'; // これを忘れると「文字列」になりません
printf("%s\n", t); // ABC
return 0;
}
ABC
終端'\0'
を忘れると未定義動作や文字化けの原因になります。
「配列のサイズは文字数+1」を常に意識しましょう。
文字列リテラルとchar配列の違い
次の表に、両者の代表的な違いをまとめます。
観点 | 文字列リテラル | char配列 |
---|---|---|
実体/配置 | プログラムの静的領域(多くは読み取り専用) | 自分の領域(自動/静的/動的いずれも可) |
型の見え方 | 式としてはchar const* に暗黙変換されがち | char[N] としてサイズが決まる |
書き換え | 不可(未定義動作) | 可能 |
作り方 | const char *p = "ABC" | char s[] = "ABC" |
サイズ | リテラル自体は長さ固定、ポインタ変数のsizeof はポインタサイズ | 配列長がコンパイル時に確定 |
有効期間 | 翻訳単位全体で有効 | 宣言した記憶域期間に従う(例: 自動ならスコープ内のみ) |
sizeof | ポインタならポインタサイズ(例: 8) | 配列全体のバイト数 |
よく似て見えても「所有するメモリか」「いつまで生きるか」「書き換え可否」が根本的に違う点を押さえてください。
書き換え: リテラルは不可/配列は可
リテラルは絶対に書き換えないのが正解です。
安全のため、const char *
を使いましょう。
#include <stdio.h>
int main(void) {
const char *p = "HELLO";
// p[0] = 'Y'; // コンパイルエラーにできるので安全
char s[] = "HELLO";
s[0] = 'Y'; // OK
printf("p=%s, s=%s\n", p, s);
return 0;
}
p=HELLO, s=YELLO
constを外してchar *p = "HELLO"
と書き、p[0] = 'Y'
すると未定義動作です。
環境によっては即クラッシュします。
サイズ: 配列は長さ固定
配列はコンパイル時に長さが決まりsizeof
で総バイト数が分かる一方、ポインタはあくまで「先の場所」を指すだけでsizeof
はポインタ自身の大きさになります。
#include <stdio.h>
int main(void) {
char a[] = "ABC"; // 4バイト('A','B','C','#include <stdio.h>
int main(void) {
char a[] = "ABC"; // 4バイト('A','B','C','\0')
const char *p = "ABC";
char b[10] = "ABC"; // 10バイト(残りは0で埋まる)
printf("sizeof(a)=%zu\n", sizeof a);
printf("sizeof(p)=%zu\n", sizeof p);
printf("sizeof(b)=%zu\n", sizeof b);
return 0;
}
')
const char *p = "ABC";
char b[10] = "ABC"; // 10バイト(残りは0で埋まる)
printf("sizeof(a)=%zu\n", sizeof a);
printf("sizeof(p)=%zu\n", sizeof p);
printf("sizeof(b)=%zu\n", sizeof b);
return 0;
}
sizeof(a)=4
sizeof(p)=8
sizeof(b)=10
上の出力は64ビット環境の一例です。
sizeof
は「配列とポインタで意味が違う」点に注意してください。
有効期間: リテラルは常に有効/配列はスコープ内
文字列リテラルはプログラム実行中ずっと有効です。
対して、関数内のchar配列はスコープを抜けると無効になります。
#include <stdio.h>
// 安全: リテラルを返す。呼び出し側でずっと使える。
const char* ok_message(void) {
return "OK";
}
// 危険: ローカル配列を返すのはNG。呼び出し時には既に無効。
// const char* bad_message(void) {
// char buf[] = "BAD";
// return buf; // ダングリングポインタ(未定義動作)
// }
int main(void) {
const char *m = ok_message();
printf("%s\n", m);
return 0;
}
OK
ローカル配列のアドレスを返さないことは、初心者の方が最初に覚えるべき重要ポイントです。
宣言と初期化の書き方
リテラルを指すポインタ
リテラルを扱うときは、読み取り専用であることを型に反映しましょう。
#include <stdio.h>
int main(void) {
const char *title = "C Language";
printf("%s\n", title);
// title[0] = 'X'; // constなので誤って書き換えようとするとコンパイルエラー
return 0;
}
C Language
constを付けるだけで「書き換え禁止」を自動検出できるので、安全性が上がります。
配列に文字列を入れる
配列は末尾の'\0'
分を含めて確保します。
初期化子がリテラルなら'\0'
は自動で入ります。
#include <stdio.h>
int main(void) {
char s1[] = "ABC"; // サイズは4
char s2[8] = "ABC"; // 残りは0で埋まる
char s3[4]; // 手動で代入する場合は終端を忘れずに
s3[0] = 'A'; s3[1] = 'B'; s3[2] = 'C'; s3[3] = '#include <stdio.h>
int main(void) {
char s1[] = "ABC"; // サイズは4
char s2[8] = "ABC"; // 残りは0で埋まる
char s3[4]; // 手動で代入する場合は終端を忘れずに
s3[0] = 'A'; s3[1] = 'B'; s3[2] = 'C'; s3[3] = '\0';
printf("[%s] [%s] [%s]\n", s1, s2, s3);
return 0;
}
';
printf("[%s] [%s] [%s]\n", s1, s2, s3);
return 0;
}
[ABC] [ABC] [ABC]
配列サイズは「最大文字数+1」を基本に見積もるのがコツです。
代入の注意: 配列に後から”ABC”は代入不可
配列は一度定義したら別の文字列を「代入」できません。
次はエラーです。
// コンパイルエラーの例(実際にはビルドしないでください)
#include <stdio.h>
int main(void) {
char s[10];
// s = "ABC"; // エラー: 配列は代入できない
return 0;
}
配列に文字列を入れ直したいなら「コピー」します。
ここでは分かりやすさ重視でsnprintf
を使います。
#include <stdio.h>
int main(void) {
char s[10] = "init";
// 安全に書き換え: sの容量を超えないようにsnprintfを使用
snprintf(s, sizeof s, "%s", "ABC");
printf("%s\n", s); // ABC
return 0;
}
ABC
「代入」ではなく「コピー」という発想が大切です。
余裕のある配列サイズと終端’\0′
終端'\0'
のぶん1文字余裕を確保します。
次のコードは間違いです。
// NG例(ビルドしないでください)
// char s[3] = "ABC"; // "ABC// NG例(ビルドしないでください)
// char s[3] = "ABC"; // "ABC\0"は4文字なので初期化子が長すぎる
"は4文字なので初期化子が長すぎる
正しくは次のようにします。
#include <stdio.h>
int main(void) {
char ok1[4] = "ABC"; // 末尾に'#include <stdio.h>
int main(void) {
char ok1[4] = "ABC"; // 末尾に'\0'が入る
char ok2[4] = {'A','B','C','\0'}; // 明示的に終端
printf("%s %s\n", ok1, ok2);
return 0;
}
'が入る
char ok2[4] = {'A','B','C','#include <stdio.h>
int main(void) {
char ok1[4] = "ABC"; // 末尾に'\0'が入る
char ok2[4] = {'A','B','C','\0'}; // 明示的に終端
printf("%s %s\n", ok1, ok2);
return 0;
}
'}; // 明示的に終端
printf("%s %s\n", ok1, ok2);
return 0;
}
ABC ABC
配列長は常に「必要文字数+1」、これを習慣にしましょう。
使い分けと注意点
文字を変更したいならchar配列
変換や編集をする予定があるなら、最初からchar配列で領域を持つのが安全です。
#include <stdio.h>
int main(void) {
char s[] = "cat";
s[0] = 'b'; // 先頭の文字を差し替え
printf("%s\n", s); // bat
return 0;
}
bat
編集対象は配列、読み取り専用はリテラルと切り分けるとミスが減ります。
読み取りだけなら文字列リテラル
定数メッセージや書式文字列など、変更しないデータはリテラルにしてconst char *
で扱うのが簡単で効率的です。
#include <stdio.h>
int main(void) {
const char *usage = "Usage: prog [options]";
printf("%s\n", usage);
return 0;
}
Usage: prog [options]
読み取り専用にしておけば、誤更新をコンパイル時に防げるメリットもあります。
よくあるミス: リテラルを書き換えてクラッシュ
// 危険な例(未定義動作、実行しないでください)
int main(void) {
char *p = "ABC"; // constなしで受けてしまった
p[0] = 'a'; // <mark style="background-color:rgba(0, 0, 0, 0);color:#cf2e2e" class="has-inline-color"><strong>未定義動作: クラッシュする可能性大</strong></mark>
return 0;
}
リテラルはconst char *
で受ける、これで防げます。
よくあるミス: s = “…” は配列ではNG
// NG: 配列は代入できない
char s[10];
// s = "Hello"; // エラー。コピー関数などを使う
配列は「代入不可・コピーで更新」というルールを覚えましょう。
よくあるミス: ‘\0’不足で文字化け
// 危険な例(実行しないでください)
char bad[3] = {'A','B','C'}; // 終端'// 危険な例(実行しないでください)
char bad[3] = {'A','B','C'}; // 終端'\0'がない → 文字列ではない
// printf("%s\n", bad); // <mark style="background-color:rgba(0, 0, 0, 0);color:#cf2e2e" class="has-inline-color"><strong>未定義動作の可能性</strong></mark>
'がない → 文字列ではない
// printf("%s\n", bad); // <mark style="background-color:rgba(0, 0, 0, 0);color:#cf2e2e" class="has-inline-color"><strong>未定義動作の可能性</strong></mark>
// 正解例
char good[4] = {'A','B','C','// 正解例
char good[4] = {'A','B','C','\0'}; // または "ABC"
'}; // または "ABC"
「文字列」=「終端'\0'
付き」という定義を常に意識してください。
まとめ
本記事では、文字列リテラルとchar配列の違いを、書き換え可否・サイズ・有効期間・初期化・よくあるミスの観点から解説しました。
要点は次の通りです。
リテラルは読み取り専用で静的に有効、配列は自前の領域を持ち編集可能だがスコープに縛られる。
配列は「必要文字数+1」で確保し終端'\0'
を忘れない。
そして配列への代入は不可、更新はコピーで行います。
これらを押さえれば、クラッシュや文字化けといった初歩的なトラブルを確実に減らせます。