C言語基礎|文字列リテラルの性質

「文字列は“永遠の箱”に置かれる?――文字列リテラルの寿命と正体をつかもう!」

文字列リテラルは“ただの文字の並び”じゃない

"ABC" のような文字列リテラルは、見た目は単なる文字の並びに見えます。
でもC言語では、文字列リテラルには独特の性質があって、ここを押さえると

  • なぜ関数の中で書いても壊れないのか
  • なぜ同じ文字列が何度も出てくるのにメモリが増えないことがあるのか
  • なぜ文字列を自由に扱うには char の配列が便利なのか

がスッキリ理解できます。

ここでは「文字列リテラルの性質」を2点、そして「文字列(string)として扱うための入れ物」まで、まとめて丁寧に見ていきますね。

文字列リテラルの性質1:静的記憶域期間で“寿命が長い”

ポイント

文字列リテラルは、静的記憶域期間を持ちます。
ざっくり言うと「プログラムの実行中ずっと存在する」イメージです。

記憶域期間のざっくり比較(イメージ)

種類いつ作られる?いつ消える?
自動記憶域期間関数内の普通の変数ブロック開始ブロック終了
静的記憶域期間文字列リテラル、static変数などプログラム開始付近プログラム終了

表の説明
関数内の変数は関数を抜けると消えますが、文字列リテラルはそうではありません。だから、関数の中に "ABCD" が書いてあっても、そのたびに作って消す、という動きを基本的にはしません。

図で理解:関数を呼んでも文字列リテラルは“残る”

文字列リテラルの性質2:同じ綴りをまとめるかは処理系依存

プログラム中に同じ綴りの文字列リテラルが複数あるとき、

  • それぞれ別々にメモリを確保する。
  • 1つだけ確保して共有する。

どちらになるかは 処理系依存です(コンパイラの方針などで変わります)。

同一文字列リテラルの格納パターン(例:"ABCD" が2回出る)

パターンメモリ上のイメージ必要な箱(char数)
個別に格納"ABCD\0" が2つ5 + 5 = 10
まとめて格納"ABCD\0" が1つを共有5

表の説明
"ABCD" は末尾のナル文字 0 まで含めて 5文字分の箱を使います。同じものが2回あると、10文字分になることもありますし、共有して5文字分で済むこともあります。どっちかは環境次第です。

重要な実務感覚

  • 内容が同じだから必ず同じ場所とは限りません。
  • 同じ場所になる可能性もあるので、ポインタ比較で同一性を決め打ちしないほうが安全です(比較は文字列比較を使うのが基本)

文字列リテラルと「文字列(string)」は似ているけど別物

文字列リテラルは、整数定数 15 や実数定数 3.14 と同じで、プログラム中に直接書ける定数です。

でも、プログラムで「文字列を自由に扱う」には、どこかの入れ物(オブジェクト)に入れるのが便利です。
そこで登場するのが char の配列です。

文字列リテラルとchar配列の役割

もの役割
文字列リテラル"ABC"定数として書ける文字の並び
char配列char s[4];文字列を格納して編集や加工しやすい

表の説明
文字列リテラルは「そのまま置いてある定数」っぽい立ち位置で、char配列は「自分で管理できる箱の列」です。学習を進めるほど、この差が効いてきます。

文字列の終端:最初に出現するナル文字が“ここまで”の目印

C言語で文字列として扱われる範囲は、

先頭から最初に出現するナル文字(値0)まで

です。

配列に格納された文字列のイメージ("ABC")

図の説明
ナル文字は「表示しないけど、終わりを示す重要な印」です。printfの %s は、この0が出るまで読み続けます。

途中に \0 がある文字列リテラルはどう見える?

"abc\0def" のように途中に \0 が入っても、リテラルとしては存在できます。
ただし 文字列として見えるのは最初の0までです。

"abc\0def" の見え方

図の説明
後ろに def が入っていても、%s のような「ナル文字終端」を前提とする扱いでは、abc までしか見えません。

サンプルプログラム

"OK" を配列に手で入れるプログラム例です。

プロジェクト名:chap9-2-1 ソースファイル名:chap9-2-1.c

// 配列に文字列を格納して表示(手作業で終端を付ける)

#include <stdio.h>

int main(void)
{
    char msg[3];    // "OK" + 終端0 の3文字分

    msg[0] = 'O';
    msg[1] = 'K';
    msg[2] = '\0';  // 終端

    printf("作ったメッセージは【%s】です。\n", msg);

    return 0;
}

このプログラムで分かること

  • char配列に文字を順に入れると文字列が作れる。
  • 最後に \0 を入れないと、%s が「どこまで表示していいか」分からず、変な出力や不具合の原因になりやすい。

printfの変換指定と、配列名を渡す意味

printf(%s)の基本

  • 書式(代表)
    int printf(const char *format, ...);
  • %s は「文字列」を表示する指定
  • 実引数に配列名 msg を渡すと、先頭要素の位置を手がかりにして、ナル文字までを文字列として表示します

printfでよく使う指定(今回の範囲)

指定意味
%s文字列(ナル文字まで)printf("%s\n", msg);
%c1文字printf("%c\n", msg[0]);

表の説明
%s は「文字の列」を扱います。%c は「単体の文字」です。同じ char を使っていても、出力の単位が違います。

登場した命令・記法の書式と役割まとめ

include

  • 書式
    #include <stdio.h>
  • 何をする?
    printf などの入出力関数を使うための宣言を取り込みます。

printf

  • 書式
    int printf(const char *format, ...);
  • 何をする?
    書式付き出力。%s はナル文字までを文字列として表示します。

puts(本文に出てきたので補足)

  • 書式
    int puts(const char *s);
  • 何をする?
    文字列を表示して改行します(内部的には最後に改行を付けて出します)。