今日まで、私はメモリ空間でfree()
を呼び出すと、他の変更を加えることなく、それを解放してさらに割り当てることができると信じていました。特に、 this SO question は、free()
がメモリをゼロにしないことを明確に示しています。
それでも、このコード(test.c)について考えてみましょう。
#include<stdlib.h>
#include<stdio.h>
int main()
{
int* pointer;
if (NULL == (pointer = malloc(sizeof(*pointer))))
return EXIT_FAILURE;
*pointer = 1337;
printf("Before free(): %p, %d\n", pointer, *pointer);
free(pointer);
printf("After free(): %p, %d\n", pointer, *pointer);
return EXIT_SUCCESS;
}
コンパイル(GCCとClangの両方):
gcc test.c -o test_gcc
clang test.c -o test_clang
結果:
$ ./test_gcc
Before free(): 0x719010, 1337
After free(): 0x719010, 0
$ ./test_clang
Before free: 0x19d2010, 1337
After free: 0x19d2010, 0
なぜそうなのですか?私はずっと嘘をついていましたか、それともいくつかの基本的な概念を誤解していましたか?それとももっと良い説明がありますか?
いくつかの技術情報:
Linux 4.0.1-1-Arch x86_64
gcc version 4.9.2 20150304 (prerelease) (GCC)
clang version 3.6.0 (tags/RELEASE_360/final)
あなたの質問に対する唯一の決定的な答えはありません。
(残りは、内部メモリプールに保持されているブロックに適用されます。)
第2に、解放されたメモリを特定の値で埋める意味はほとんどありませんが(アクセスすることは想定されていないため)、そのような操作のパフォーマンスコストはかなり高くなる可能性があります。これが、ほとんどの実装が解放されたメモリに対して何もしない理由です。
第三に、デバッグ段階で、解放されたメモリを事前に決定されたガベージ値で埋めることは、エラー(すでに解放されたメモリへのアクセスなど)をキャッチするのに役立ちます。そのため、標準ライブラリの多くのデバッグ実装は、解放されたメモリを事前に決定された値で埋めます。パターン。 (ゼロ、ところで、そのような値には最適な選択ではありません。0xDEADBABE
パターンのようなものの方がはるかに理にかなっています。)しかし、これも、パフォーマンスへの影響が問題にならないデバッグバージョンのライブラリでのみ実行されます。 。
第4に、ヒープメモリ管理の多くの(最も)一般的な実装では、解放されたブロックの一部を内部目的で使用します。つまり、そこにいくつかの意味のある値を格納します。これは、ブロックのその領域がfree
によって変更されることを意味します。しかし、一般的には「ゼロ化」されていません。
もちろん、これはすべて実装に大きく依存します。
一般に、元の信念は完全に正しいです。コードのリリースバージョンでは、解放されたメモリブロックはブロック全体の変更を受けません。
free()
は、原則としてメモリをゼロにしません。 malloc()
への将来の呼び出しで再利用するためにリリースするだけです。特定の実装は既知の値でメモリをいっぱいにする可能性がありますが、それは純粋にライブラリの実装の詳細です。
Microsoftのランタイムは、解放され割り当てられたメモリを有用な値でマークすることをうまく利用しています(詳細については、 Visual Studio C++では、メモリ割り当ての表現は何ですか? を参照してください)。また、実行すると明確に定義されたトラップが発生する値で埋められていることも確認しました。
より良い説明はありますか?
有る。 free()
dになった後でポインタを逆参照すると、動作が未定義になるため、実装には、メモリ領域がゼロで埋められていると思わせる行為など、好きなことを実行する権限があります。
あなたが実際に知らなかったかもしれないもう一つの落とし穴がここにあります:
free(pointer);
printf("After free(): %p \n", pointer);
readingpointer
の後のfree
の値でさえ、ポインタが不確定になるため、未定義の動作です。
もちろん、以下の例のように、解放されたポインタを逆参照することも許可されていません。
free(pointer);
printf("After free(): %p, %d\n", pointer, *pointer);
ps。一般に、アドレスを%p
で出力する場合(printf
のように)、アドレスを(void*)
にキャストします。 (void*)pointer
-それ以外の場合は、未定義の動作も発生します
Free()はメモリをゼロにしますか?
いいえ。 glibc malloc実装 は、内部ハウスキーピングデータの以前のユーザーデータのポインターの最大4倍のサイズを上書きする可能性があります。
詳細:
以下は、glibcの_malloc_chunk
_構造です( ここ を参照)。
_struct malloc_chunk {
INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */
INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */
struct malloc_chunk* fd; /* double links -- used only if free. */
struct malloc_chunk* bk;
/* Only used for large blocks: pointer to next larger size. */
struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
struct malloc_chunk* bk_nextsize;
};
_
割り当てられたメモリチャンク内のユーザーデータのメモリ領域は、size
エントリの後に始まります。 free
が呼び出された後、ユーザーデータが使用されたメモリスペースは、空きメモリチャンクのリストに使用される可能性があるため、前のユーザーデータの最初の4 * sizeof(struct malloc_chunk *)
バイトはおそらく上書きされ、したがって別の以前のユーザーデータ値よりも値が出力されます。それは未定義の振る舞いです。割り当てられたブロックが大きい場合、セグメンテーション違反が発生している可能性があります。
他の人が指摘しているように、free
dポインタを使って何もすることは許可されていません(そうでなければ、恐ろしい 未定義の振る舞い 、常にすべきです避けてください。 this を参照してください)。
実際には、単純にコーディングしないことをお勧めします
free(ptr);
しかし、常にコーディング
free(ptr), ptr=NULL;
(実際には、これは、double free
sを除いて、いくつかのバグを見つけるのに大いに役立ちます)
その後ptr
が使用されない場合、コンパイラーはNULL
からの割り当てをスキップして最適化します。
実際には、コンパイラはfree
とmalloc
について知っています(標準Cライブラリヘッダーはおそらくこれらの標準関数を適切な 関数属性 で宣言するためです-両方によって理解されます- [〜#〜] gcc [〜#〜] & Clang/LLVM )したがって、コードを最適化できる可能性があります(malloc
&の標準仕様に従って) free
....)ですが、malloc
とfree
の実装は、多くの場合、C標準ライブラリによって提供されます(たとえば、非常に頻繁にGNU = glibc または musl-libc Linuxの場合)したがって、実際の動作はlibc
(コンパイラ自体ではありません)によって提供されます。適切なドキュメントを読んでください。特に- free(3) manページ。
ところで、Linuxでは、glibc
とmusl-libc
はどちらもフリーソフトウェアなので、ソースコードを調べて動作を理解することができます。 mmap(2) のようなシステムコールを使用してカーネルから仮想メモリを取得することがあります(後で munmap(2) を使用してメモリをカーネルに解放します)が、彼らは通常、以前のfree
dメモリを将来のmalloc
sのために再利用しようとします
実際には、free
はあなたのメモリをmunmap
することができます(特にbigメモリmalloc
- atedゾーンの場合)-そして、あえて(後で)そのSIGSEGV
dポインタを逆参照すると、free
が得られますが、多くの場合(特にsmallメモリゾーン)後でそのゾーンを再利用することができます。正確な動作は実装によって異なります。通常、free
はnotで、解放されたばかりのゾーンをクリアまたは書き込みます。
libtcmalloc などの特別なライブラリをリンクすることで、独自のmalloc
とfree
を再定義(つまり再実装)することもできます。 C99またはC11標準の規定と互換性のある動作。
Linuxでは、メモリのオーバーコミットを無効にして valgrind を使用します。 gcc -Wall -Wextra
(およびデバッグ時にはおそらく-g
)を使用してコンパイルします。少なくともいくつかのいたずらなバグを探すために、-fsanitize=address
を最近のgcc
またはclang
に渡すことも検討してください。 。)。
ところで、時々ベームの保守的なガベージコレクター は役に立つかもしれません。 (プログラム全体で)malloc
の代わりにGC_MALLOC
を使用し、free
- ingメモリを気にしません。
free()
は、実際にメモリをオペレーティングシステムに戻し、プロセスを小さくすることができます。通常、実行できるのは、後でmallocを呼び出すことを許可することですスペースを再利用するため。その間、スペースはmalloc
によって内部的に使用されるフリーリストの一部としてプログラムに残ります。