動的な文字列

64バイトの器はいつも無駄か足りないかのどちらか

ここまでの回では、名前を受け取るのに char name[64] のような固定の器を使ってきました。この書き方には、いつも同じ悩みが付いてきます。"Ito" を入れるときは61バイトが遊び、それより長い名前が来たら切り詰めるしかありません。人数が増えるほど、無駄も切り詰めも積み上がります。

第2章で覚えた malloc を使うと、長さが分かってからその長さぶんだけ確保できます。文字列の長さは strlen で分かるので、必要な量は計算できます。

strlen + 1 の 1 は何か

char *p = malloc(my_strlen(s) + 1);

+ 1 は終端の '\0' の場所です。第1回で「見えている文字数と必要なバイト数は1ずれる」と書いたのが、ここで効いてきます。この + 1 を落とすと、文字は全部入るのに終端だけがはみ出す、という壊れ方をします。表示すると後ろのごみが続けて出るか、そのまま落ちます。

写すときは終端まで含めて運びます。i <= len と、等号が入っているところが要点です。

for (int i = 0; i <= len; i++) { p[i] = s[i]; }

i < len にすると終端が写らないので、せっかく確保した1バイトが使われないまま終わります。

確保したものは誰のものか

malloc が返した領域は、free を呼ぶまで生き続けます。関数を抜けても消えません。これが char name[64] のようなローカル配列との決定的な違いです。第2章で「関数の中のローカル変数のアドレスを返してはいけない」と学びましたが、malloc した領域なら返して構いません。呼び出し元がそれを受け取り、そして 解放する責任も一緒に受け取ります

つまり my_strdup のような関数を書いたら、それは「使い終わったら free してください」という約束を呼び出し側に押し付ける関数だということです。標準ライブラリの説明で「返り値は free すること」と書かれているのは、この責任の受け渡しを言葉にしたものです。

責任の所在があいまいなまま人数が増えると、解放漏れが静かに積もります。今回は確保した回数と解放した回数を自分で数えて、数字が一致することを目で確かめます。数が合っていれば、少なくとも数え漏らしはありません。

解放したあとのポインタ

free(p) を呼んでも、p の中身は消えません。手放したはずの番地がそのまま残ります。この状態のポインタを使うと、すでに他人のものになった領域を読み書きすることになります。落ちるとは限らず、しばらく普通に動いてから遠くで壊れるので、原因を追うのが非常に厄介です。

対策は簡単で、free した直後に p = NULL; と入れておきます。NULL は誰も指していないことを表す特別な値なので、うっかり使えばその場で落ちます。静かに壊れるより、その場で落ちてくれるほうがずっと安全です。

では、名前を必要な長さだけ確保して保持するプログラムを書いてみましょう。

要件

  1. my_strdup という関数を書く。malloc で長さ+1バイト確保して写す
  2. 確保するたびに alloc_count を1増やす
  3. 各名前を Tanaka 6文字 の形で1行ずつ表示する
  4. 全部 free して、解放するたびに free_count を1増やす
  5. 最後に 確保3回 解放3回 の形で表示する

入出力例

main("Tanaka Suzuki Sato") → "Tanaka 6文字 Suzuki 6文字 Sato 4文字 確保3回 解放3回" main("Ito Yamamotokurosawa") → "Ito 3文字 Yamamotokurosawa 16文字 確保2回 解放2回" main("Nakamura") → "Nakamura 8文字 確保1回 解放1回"

ヒント

生田 陸人
ゆめさくエンジニア / 現役ソフトウェアエンジニア
編集 LuaGate編集部
main.c
学習モード

メモ

動的な文字列

⌘S で保存