🇯🇵 日本語 | 🇺🇸 English

第41回 確認問題(動的メモリ)

malloc/free、ダングリングポインタ、メモリリーク、callocの理解度を確認しましょう。

確認問題1 ― mallocの基本

int *p = (int *)malloc(sizeof(int) * 3);
p[0] = 10; p[1] = 20; p[2] = 30;
printf("%d\n", p[1]);
free(p);

出力結果は?

10
20
30
コンパイルエラー
解説: malloc(sizeof(int)*3) で int 3個分のヒープ領域を確保。p[1] は2番目の要素 = 20

確認問題2 ― free後のアクセス

int *p = (int *)malloc(sizeof(int));
*p = 42;
free(p);
printf("%d\n", *p);

このコードはどうなる?

42
0
未定義動作(ダングリングポインタ)
コンパイルエラー
解説: free後のポインタ(ダングリングポインタ)へのアクセスは未定義動作
値が残っているように見えることもありますが保証されません。free後は p = NULL; にするのが安全です。

確認問題3 ― メモリリーク

void func(void) {
    int *p = (int *)malloc(sizeof(int) * 100);
    *p = 42;
    // free(p) を忘れた
}

このコードの問題は?

コンパイルエラー
実行時エラー
メモリリーク(動くが領域が解放されない)
何も問題はない
解説: free を呼ばずに関数を抜けると、ポインタが失われ確保した領域を返却できなくなります。
これがメモリリーク。プログラムは動きますが、繰り返すとメモリを圧迫します。

確認問題4 ― callocとmalloc

int *a = (int *)calloc(5, sizeof(int));
printf("%d %d %d\n", a[0], a[2], a[4]);
free(a);

出力結果は?

不定値(ゴミ)
0 0 0
コンパイルエラー
Segmentation Fault
解説: calloc は malloc と違い、確保した領域を0で初期化します。
よってすべて 0 0 0。初期値が0でよい場合は calloc が便利です。

確認問題5 ― 二重free

int *p = (int *)malloc(sizeof(int));
*p = 10;
free(p);
free(p);  // 同じポインタをもう一度free

このコードはどうなる?

問題なく実行される
コンパイルエラー
未定義動作(クラッシュやヒープ破壊)
2回目の free は無視される
解説: すでに解放済みのポインタに対する free は未定義動作(double free)
  • 多くの環境でクラッシュやヒープの内部構造破壊を引き起こす
  • セキュリティ脆弱性(任意コード実行)につながることもある
予防策: free 後にポインタを NULL に設定する。free(NULL) は安全(何もしないことが規格で保証されている)。

確認問題6 ― 動的配列のsizeof

// ポインタは8バイト、int は4バイトの環境
int *p = (int *)malloc(sizeof(int) * 10);
printf("%lu\n", sizeof(p));

出力結果は?

40(int 10個分)
10
8(ポインタのサイズ)
4
解説: pポインタ変数なので、sizeof(p)ポインタそのもののサイズを返します。
  • 配列と違い、malloc で確保した領域のサイズは sizeof では取れない
  • 64bit環境では通常 8 バイト
確保したサイズはプログラム側で別途変数として覚えておく必要があります(malloc はサイズを返さないため)。

確認問題7 ― reallocの挙動

int *p = (int *)malloc(sizeof(int) * 3);
int *q = p;          // p をバックアップ
p = (int *)realloc(p, sizeof(int) * 100);
// この後、qを使うとどうなる?

realloc 後の q について正しいのは?

必ず p と同じ領域を指しているので安全に使える
領域が移動した場合 q は無効になる(使用は未定義動作)
realloc 後は自動的に q も更新される
q は必ず NULL になる
解説: realloc は既存領域を拡張できない場合、新しい場所にコピーして古い領域を解放します。
  • 拡張できれば同じアドレスが返る → q も有効
  • 移動した場合、古いアドレス q は解放されたため無効(ダングリング)
  • 移動するかどうかは実装依存で予測できない
realloc を呼んだ後は、元のポインタを使ってはいけません。返り値で受けた新しいポインタだけを使います。また、realloc が失敗すると NULL が返り元のポインタは解放されないので、tmp = realloc(p, ...); if (tmp) p = tmp; のパターンが安全です。

確認問題8 ― mallocの失敗

int *p = (int *)malloc(sizeof(int) * 1000000000);
*p = 42;  // NULLチェックなし

メモリ確保に失敗した場合どうなる?

p には自動的に有効なポインタが入る
コンパイルエラー
p が NULL になり、*p で Segmentation Fault
*p には 0 が代入される
解説: malloc は確保に失敗すると NULL を返します
  • NULLチェックをせず *p するとNULL参照でクラッシュ
  • 必ず if (p == NULL) { エラー処理 } を書くべき
安全な書き方:
int *p = malloc(...);
if (p == NULL) {
    fprintf(stderr, "メモリ不足\n");
    return 1;
}

確認問題9 ― free(NULL)

int *p = NULL;
free(p);  // NULLポインタをfree
printf("OK\n");

このコードはどうなる?

Segmentation Fault
何も起きず、OK が表示される
コンパイルエラー
未定義動作
解説: free(NULL)規格で「何もしない」ことが保証されています。
  • NULLチェックなしで free を呼べるので、エラー処理のコードがシンプルになる
  • if (p != NULL) free(p); のような条件分岐は不要
なお、free 済みのポインタ(NULL ではない無効なポインタ)を再度 free すると未定義動作になります。free 後は p = NULL; にするのが安全なイディオムです。

結果

回答してください
動的メモリの講座に戻るホームへ
← 前の講座
第40回 動的メモリ(malloc/free)
次の講座 →
第42回 ビット演算

この講座の理解を深めるおすすめ書籍

サイトで動きを理解し、書籍で演習量を補うと効果的です

📘
苦しんで覚えるC言語
MMGames 著
初心者向けの定番入門書。
Amazonで見る
📗
新・明解C言語 入門編
柴田望洋 著
図解が豊富で演習問題も充実。
Amazonで見る
📙
プログラミング言語C 第2版
B.W.カーニハン, D.M.リッチー 著
通称K&R。C言語の原典。
Amazonで見る

※ 上記リンクはアフィリエイトリンクです。購入によりサイト運営を支援いただけます。

この記事をシェア
X(Twitter)でシェア Facebookでシェア LINEで送る はてブ