【ダブルフリー】C言語のfree()で痛い目を見ないただ一つの方法【予防】

25, 2019-07-03

目次

ダブルフリーでプログラムがクラッシュ!

クラッシュした飛行機

こんにちは、narupoです。

C言語、書いてますか?
おもしろいですよね、C言語。


C言語ではポインタを使いますが、このポインタの扱い方を間違えるとたいへんです。

プログラムがドーン! とクラッシュするかもしれません。

そう、ダブルフリーでね。


そもそもダブルフリーとは?

そもそもダブルフリーとは何なのか


みなさん、C言語の動的メモリの確保は知ってますよね?

mallocとかcallocとかreallocとか使う、アレです。


これらの関数を使った後にかならずセットで使う関数がありますよね?

そう、free関数です。


freeは、mallocなどで確保した動的メモリを開放する関数です。


そして、一度free()したメモリへのポインタを再度free()することを、

ダブルフリーと言います。


#include <stdlib.h>

int main(void) {
    char *ptr = malloc(100); // メモリを確保
    free(ptr); // メモリを解放
    free(ptr); // ダブルフリー!
}

ダブルフリーを予防する理由

ガスマスク1

なぜダブルフリーを予防したほうがいいのか?


実は、確保したメモリへのポインタをfreeで解放しても、そのポインタの指しているアドレスはそのままです。

そのアドレスは、すでに解放されたメモリへのアドレスを指しています。

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    char *ptr = malloc(100); // メモリを確保
    free(ptr); // メモリを解放
    printf("%p\n", ptr); // ptrには解放されたアドレスが入ったまま
    return 0;
}

このポインタを再度free()してしまうとどうなるのか。

ダブルフリーが起こるとどうなる?

すでにfree()したポインタを再度free()すると、なんとプログラムがクラッシュしてしまいます。


実際にやってみましょう。

以下がダブルフリーのサンプルです。

#include <stdlib.h>

int main(void) {
    char *ptr = malloc(100); // メモリを確保
    free(ptr); // メモリを解放
    free(ptr); // ダブルフリー!
    return 0;
}


gccでは、このプログラムを実行すると↓のメッセージが表示され、プログラムがクラッシュします。

*** Error in `./double_free_sample.out': double free or corruption (fasttop): 0x0000000001296010 ***
Aborted (core dumped)


このダブルフリー、大きなプログラムの開発だと、どこかでこっそり起こったりしたらデバッグが大変です。

目grepでダブルフリーを起こしているfreeを見つけなければいけません。

あるいは、valgrindなどのツールの力を借りてデバッグする必要があります。


ダブルフリー、起こったら面倒ですよね。

できればダブルフリーを起こしたくないというのがプログラムの親心というものです。

ダブルフリーを予防するにはどうしたらいいのか?

注射器1

では、ダブルフリーを予防するにはどうしたらいいのか?


これは簡単です。

具体的には、こうすればいいのです。

#include <stdlib.h>

int main(void) {
    char *ptr = malloc(100);
    free(ptr);
    ptr = NULL; // 使い終わったポインタをNULLクリア
}

↑のコードでは、ポインタをfree()したあとに、ポインタをNULLクリアしています。


なぜこうすればダブルフリーを防げるのか?


free関数のマニュアルを見てみましょう。

$ man 3 free

すると、マニュアルには以下のように書いてあります。

If ptr is NULL, no operation is performed
(ptrがNULLの場合、何もしません。)


つまり、NULLポインタをfree()しても何も起こらないということですね。


ということは、以下のコードが合法になるわけです。

#include <stdlib.h>

int main(void) {
    char *ptr = malloc(100);
    free(ptr);
    ptr = NULL; // 使い終わったポインタをNULLクリア
    free(ptr); // ダブルフリー?
}

使い終わったptrにはNULLポインタが入っているので、このプログラムにエラーは起こりません。


ダブルフリーを予防できるのはわかったけど、使い終わったポインタをいちいちNULLクリアするのはめんどくさいですよね。

それもプログラムの親心というものです(特に優秀なプログラマーは面倒くさがり屋が多いですからネ)。


ご安心ください、そんなあなたにおススメしたい本日の商品がこちらです。

ダブルフリーを予防する頼もしいマクロ、SAFE_FREE!

正拳突き1

こんなマクロを定義してください。

#define SAFE_FREE(ptr) { \
    free(ptr); \
    ptr = NULL; \
} \

このマクロは「安全なfree」を実現できるマクロです。


実際に使ってみましょう。

#include <stdlib.h>

#define SAFE_FREE(ptr) { \
    free(ptr); \
    ptr = NULL; \
} \

int
main(void) {
    char *ptr = malloc(100);
    SAFE_FREE(ptr);
    SAFE_FREE(ptr); // ダブルフリー?
    return 0;
}

マクロによってたった1行で安全なfree()を実現できました!

このプログラムは安全です


皆さんのコードに、NULLクリアを追加して回る手間は必要ありません。

このマクロをどこかで定義したら、free関数をSAFE_FREEに置き換えするだけで済みます。

マクロを使って快適なC言語ライフを

マクロは使いどころが正しければ、プログラミングをとても効率よくしてくれます。

C言語のメモリ管理はむずかしいですが、このマクロを使えば少し楽になりますね。

それでは、みなさんも良きC言語ライフを。

スポンサーリンク

スポンサーリンク

スポンサーリンク

スポンサーリンク