Menu

C言語の malloc: 動的メモリ確保、free、そして sizeof

ヒープが存在する理由、malloc が実行時に決まるサイズのメモリを渡す仕組み、p = malloc(n * sizeof *p) のイディオム、NULL のチェック、ちょうど1回だけ解放すること、そして動的配列を最初から最後まで。

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

ここまでに書いてきた配列は、どれもサイズがコンパイル時に焼き付けられていました。int scores[10]; は、3つ必要でも300必要でも10個の枠を確保します。それはサイズがプログラムの実行中にしか分からないときまでは通用します - ファイルに何行あるか、ユーザーが何件のレコードを入力するか、といった場合です。動的メモリがそれを解決します。必要なバイトを、必要なときに要求し、終わったら返すのです。

道具は <stdlib.h> にあります:

#include <stdlib.h>

ヒープが存在する理由

C言語はプログラムに2つの主要なメモリ領域を与えます。ローカル変数は自動記憶域(スタック)にあり、コンパイラがそのサイズを決め、関数が戻った瞬間に消えます。動的な確保はヒープにあり、サイズは実行時にあなたが決め、明示的に解放するまでブロックは生き続けます。

この2つ目の性質こそが malloc を使う本当の理由です。関数はブロックを確保してポインタを返すことができ、そのメモリは呼び出し側でもまだ有効です - ローカル配列には決してできないことです。2つの領域はスタックとヒープで詳しく比較しています。

malloc: バイトを要求する

malloc はバイト数を取り、その数だけの未初期化メモリを指す void * を返します。失敗した場合は NULL です。

この短いプログラムにある4つのことが、この分野の作法のすべてです:

  1. サイズの式は n * sizeof *scoresscores が指すものの n 個分」と読みましょう。
  2. 結果は使用前に NULL と照合される。
  3. ブロックは配列とまったく同じように使われる - インデックス指定はポインタ演算なので scores[i] が動きます。
  4. 1回だけ解放され、その後ポインタが NULL に設定される。

戻り値にキャストがないことに注目してください。C言語では void * は任意のオブジェクトポインタに自動変換され、(int *)malloc(...) と書くのはノイズを増やしたうえ <stdlib.h> の include 漏れを隠してしまいます。(C++ ではキャストが必要ですが、C言語では不要です。)

sizeof のイディオム

なぜ sizeof(int) ではなく sizeof *scores なのか。古びることがないからです。

int  *a = malloc(n * sizeof *a);     /* n 個の int、ここでの int が何であれ */
long *b = malloc(n * sizeof *b);     /* 同じ形の行で、正しいサイズ */

/* こわれやすい形 */
long *c = malloc(n * sizeof(int));   /* コンパイルは通るが、確保が足りない */

最後の行は、どのコンパイラも警告してくれない本物のバグです。clong * なのに確保は int のサイズで行われ、前半を越えるすべての書き込みが末尾からはみ出します。sizeof *c にすれば、サイズは宣言に自動的に追従します。

sizeof *pp を参照外ししません - sizeof は型だけからコンパイル時に評価されるので、p が未初期化でも NULL でも安全です。

未初期化のメモリ

malloc は渡すものをクリアしません。バイトには以前そこにあったものが入っています。

ブロックをゼロクリアしてほしいときは、calloc が一手でやってくれます - calloc と reallocを参照してください。

free: 返す

free(p) はブロックをアロケータに返します。ルールは3つ:

  • malloc が返したまさにそのポインタを渡すこと。進めたものではいけません。free(p + 1) は未定義動作です。
  • 各ブロックをちょうど1回解放すること。2回は二重解放で、アロケータ自身の管理情報を壊します。
  • 解放後、そのポインタは無効です。使うとuse-after-free になります。
int *p = malloc(sizeof *p);
free(p);
*p = 5;        /* use-after-free - 未定義動作 */
free(p);       /* 二重解放 - 未定義動作 */

どちらの行もすぐにクラッシュする必要はありません。それこそが危険なところで、プログラムは数分動き続けてから、まったく無関係な場所で落ちることがあります。安上がりな防御はポインタを空にすることです:

free(p);
p = NULL;

free(NULL) は何もしないことが明示的に定義されているので、その後の2回目の free(p) は無害になり、*p は静かな破壊ではなく本当の障害箇所での即座のクラッシュになります。

動的配列を最初から最後まで

実際の確保の形はこうです。実行時にカウントを読み、それをもとにブロックのサイズを決め、使い、解放します。

カウントを size_t にキャストすることは、サイズが大きくなると効いてきます。素の intn による n * sizeof *values は、掛け算の結果が malloc に届く前にオーバーフローし得て、意図よりはるかに小さいブロックができてしまいます。size_t で掛ければそれを避けられます。

関数の中で確保する

ヒープの生存期間のルールこそが、これを合法にし、そしてローカル配列を返すことを違法にしているものです:

len + 1 はヌル終端の場所を残しています - 文字列のあらゆる場所と同じサイズ計算のルールです。そして関数の上のコメントに注目してください。関数が確保したメモリを返すとき、誰がそれを解放するかはその契約の一部です。それを書き留めることは事務作業ではありません。リークを防ぐ唯一のものなのです。

ヒープ上の構造体

同じイディオムがどんな型にも通用します:

2つ目の失敗の分岐が、return する前に p を解放していることに注目してください。関数が2つ以上の確保を抱えるようになると、すべてのエラー経路で、すでに取ったものを解放しなければなりません - メモリリークで説明するリークの最もよくある原因です。

チェックリスト

  • 最初に使う前に、必ず mallocNULL と照合すること。
  • サイズは n * sizeof *p とし、掛け算は size_t で行うこと。
  • C言語では戻り値にキャストを付けないこと。
  • malloc のメモリは未初期化。読む前に書くこと。
  • 元のポインタでちょうど1回だけ解放し、その後 NULL に設定すること。
  • 確保の後のすべてのエラー経路は、すでに抱えているものを解放すること。

よくある質問

C言語の malloc は何をしますか?

malloc(n) はヒープから n バイトのメモリを要求し、そのブロックの先頭へのポインタを返します。要求を満たせなければ NULL を返します。そのブロックは、同じポインタを free に渡すまで生き続けます - ローカル変数と違い、関数が戻っても解放されません。

C言語で malloc と free はどう使いますか?

int *p = malloc(n * sizeof *p); で確保し、触る前に if (p == NULL) を確認し、配列のように使い、終わったら free(p); をちょうど1回だけ呼びます。その後 p = NULL としておくと、後でうっかり使ったときに静かな破壊ではなくきれいなクラッシュになります。

malloc(n * sizeof *p) が malloc(n * sizeof(int)) より良いのはなぜですか?

sizeof *p はポインタの型に自動的に追従するからです。後で plong *struct Point * になっても、確保サイズはひとりでに更新されます。sizeof(int) と書き下すと、もう誤りになったサイズが残り、コンパイラはそれを指摘してくれません。

C言語でメモリを free しないとどうなりますか?

そのブロックはプロセスが生きている間ずっと確保されたままになります - メモリリークです。短いプログラムなら、終了時にOSがすべて回収するので済んでしまいますが、長く走り続けるプログラムは一巡ごとに少しずつ漏らし、やがてメモリを使い果たします。メモリリークを参照してください。

Coddy programming languages illustration

Coddyでコードを学ぼう

始める