C言語入門|文字列のためのメモリ確保方法

ここまでで、私たちは C言語の文字列について、かなり深いところまで理解してきました。

  • 文字列の正体は char 配列であること
  • 文字列の終わりは \0 で決まること
  • 文字列は先頭アドレスでやり取りされること

このあたりは、もう「なるほど」と腹落ちしてきた頃でしょう。

ところが、ここで1つ気になるポイントが出てきます。
それが、文字列の業界ルール第4条 です。

業界ルール第4条の意味

文字列に関する業界ルールの第4条は、こう定めています。

配列を複製するメモリ領域を確保する手段は問わない。

一見すると、少しあいまいな表現に見えますね。
でも、これが意味しているのは、実はとても重要なことです。

文字列として扱えるかどうかは、
char 配列かどうかでは決まらない

という考え方です。

文字列に必要なのは「連続したメモリ」

C言語において「文字列」として扱われるために必要な条件は、とてもシンプルです。

  • char が1文字ずつ順に並んでいる。
  • 最後に \0 が置かれている。
  • その先頭アドレスを管理できる。

この条件さえ満たしていれば、
どこに確保されたメモリであっても文字列として扱える
というのが、業界ルール第4条の本質です。

つまり、

文字列とは「データの並び方」であって
「確保方法」そのものではない

というわけです。

文字列のためのメモリ確保は3通りある

文字列を格納するための連続したメモリ空間を確保する方法は、実は大きく分けて次の3つしかありません。

手段確保方法確保される領域
手段①配列宣言スタック領域
手段②mallocヒープ領域
手段③文字列リテラル静的領域

この3つは、すべて文字列を実現できる という点では共通しています。
違うのは、「どこに」「どんな寿命で」確保されるかです。

配列宣言による文字列(スタック領域)

もっともなじみ深いのが、配列宣言による方法です。

char array[1024] = "C";
char* msg = array;

この方法では、

  • メモリはスタック領域に確保される。
  • 関数が終了すると寿命が尽きる。

という特徴があります。

手軽で安全に見えますが、
「関数の外に持ち出せない」という制約がある点には注意が必要です。

malloc による文字列(ヒープ領域)

次に、第10章で学んだ malloc を使う方法です。

char* msg = (char*)malloc(1024);
msg[0] = 'C';
msg[1] = '\0';

この方法では、

  • メモリはヒープ領域に確保される。
  • 関数が終了しても寿命は尽きない。
  • free を忘れるとメモリリークになる。

という特徴があります。

柔軟で強力ですが、
管理責任がプログラマにある という点が最大のポイントです。

文字列リテラルによる文字列(静的領域)

そして3つ目が、少し意外に感じるかもしれない方法です。

const char* msg = "C";

これは見た目こそ「ただの文字」ですが、
実際には メモリ確保が行われています

プログラム起動時に、

  • ソースコード中の文字列リテラル
  • 書式文字列("%s" など)

はすべて 静的領域 に読み込まれます。

msg には、その静的領域にある
文字列 "C" の先頭アドレスが入っている、というわけです。

なぜ const char* なのか

文字列リテラルを指すポインタには、通常 const char* が使われます。

理由はとても現実的です。

  • 静的領域は読み込み専用になる場合がある。
  • 書き換えようとすると実行時エラーになる可能性がある。

そのため、

書き換えてはいけないメモリを指している

という意思表示として、const が付けられます。

C言語では警告で済む処理系もありますが、
C++ では非推奨となるため、
リテラルを指すポインタには const を付ける
というのが現在の安全な文化です。

同じ「文字列」でも性質はまったく違う

ここまでをまとめると、次のようになります。

変数実体寿命解放
msg1スタック上の配列関数終了まで自動
msg2ヒープ領域free まで手動
msg3静的領域プログラム終了まで不要

見た目はどれも char* で扱える「文字列」ですが、
裏側の性質はまったく別物 です。

ここからが本番

この違いを理解せずに文字列を扱うと、

  • 寿命切れのメモリを参照する。
  • 書き換えてはいけない領域を書き換える。
  • free し忘れてメモリリークする。

といった事故につながります。

逆に言えば、

どの領域に、どの方法で文字列を確保しているか

を意識できるようになると、
C言語の文字列操作は一気に見通しがよくなります。