web-dev-qa-db-ja.com

free()はメモリをゼロにしますか?

今日まで、私はメモリ空間で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)
36
browning0

あなたの質問に対する唯一の決定的な答えはありません。

  • まず、解放されたブロックの外部動作は、システムに解放されたか、プロセスまたはCランタイムライブラリの内部メモリプールに解放されたブロックとして格納されたかによって異なります。最新のOSでは、「システムに戻された」メモリにプログラムからアクセスできなくなります。つまり、メモリがゼロにされたかどうかの問題は重要ではありません。

(残りは、内部メモリプールに保持されているブロックに適用されます。)

  • 第2に、解放されたメモリを特定の値で埋める意味はほとんどありませんが(アクセスすることは想定されていないため)、そのような操作のパフォーマンスコストはかなり高くなる可能性があります。これが、ほとんどの実装が解放されたメモリに対して何もしない理由です。

  • 第三に、デバッグ段階で、解放されたメモリを事前に決定されたガベージ値で埋めることは、エラー(すでに解放されたメモリへのアクセスなど)をキャッチするのに役立ちます。そのため、標準ライブラリの多くのデバッグ実装は、解放されたメモリを事前に決定された値で埋めます。パターン。 (ゼロ、ところで、そのような値には最適な選択ではありません。0xDEADBABEパターンのようなものの方がはるかに理にかなっています。)しかし、これも、パフォーマンスへの影響が問題にならないデバッグバージョンのライブラリでのみ実行されます。 。

  • 第4に、ヒープメモリ管理の多くの(最も)一般的な実装では、解放されたブロックの一部を内部目的で使用します。つまり、そこにいくつかの意味のある値を格納します。これは、ブロックのその領域がfreeによって変更されることを意味します。しかし、一般的には「ゼロ化」されていません。

もちろん、これはすべて実装に大きく依存します。

一般に、元の信念は完全に正しいです。コードのリリースバージョンでは、解放されたメモリブロックはブロック全体の変更を受けません。

26
AnT

free()は、原則としてメモリをゼロにしません。 malloc()への将来の呼び出しで再利用するためにリリースするだけです。特定の実装は既知の値でメモリをいっぱいにする可能性がありますが、それは純粋にライブラリの実装の詳細です。

Microsoftのランタイムは、解放され割り当てられたメモリを有用な値でマークすることをうまく利用しています(詳細については、 Visual Studio C++では、メモリ割り当ての表現は何ですか? を参照してください)。また、実行すると明確に定義されたトラップが発生する値で埋められていることも確認しました。

16
D.Shawley

より良い説明はありますか?

有る。 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-それ以外の場合は、未定義の動作も発生します

9
giorgim

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 *)バイトはおそらく上書きされ、したがって別の以前のユーザーデータ値よりも値が出力されます。それは未定義の振る舞いです。割り当てられたブロックが大きい場合、セグメンテーション違反が発生している可能性があります。

8
4566976

他の人が指摘しているように、freedポインタを使って何もすることは許可されていません(そうでなければ、恐ろしい 未定義の振る舞い 、常にすべきです避けてください。 this を参照してください)。

実際には、単純にコーディングしないことをお勧めします

free(ptr);

しかし、常にコーディング

free(ptr), ptr=NULL;

(実際には、これは、double freesを除いて、いくつかのバグを見つけるのに大いに役立ちます)

その後ptrが使用されない場合、コンパイラーはNULLからの割り当てをスキップして最適化します。

実際には、コンパイラはfreemallocについて知っています(標準Cライブラリヘッダーはおそらくこれらの標準関数を適切な 関数属性 で宣言するためです-両方によって理解されます- [〜#〜] gcc [〜#〜]Clang/LLVM )したがって、コードを最適化できる可能性があります(malloc&の標準仕様に従って) free....)ですが、mallocfreeの実装は、多くの場合、C標準ライブラリによって提供されます(たとえば、非常に頻繁にGNU = glibc または musl-libc Linuxの場合)したがって、実際の動作はlibc(コンパイラ自体ではありません)によって提供されます。適切なドキュメントを読んでください。特に- free(3) manページ。

ところで、Linuxでは、glibcmusl-libcはどちらもフリーソフトウェアなので、ソースコードを調べて動作を理解することができます。 mmap(2) のようなシステムコールを使用してカーネルから仮想メモリを取得することがあります(後で munmap(2) を使用してメモリをカーネルに解放します)が、彼らは通常、以前のfreedメモリを将来のmallocsのために再利用しようとします

実際には、freeはあなたのメモリをmunmapすることができます(特にbigメモリmalloc- atedゾーンの場合)-そして、あえて(後で)そのSIGSEGVdポインタを逆参照すると、freeが得られますが、多くの場合(特にsmallメモリゾーン)後でそのゾーンを再利用することができます。正確な動作は実装によって異なります。通常、freenotで、解放されたばかりのゾーンをクリアまたは書き込みます。

libtcmalloc などの特別なライブラリをリンクすることで、独自のmallocfreeを再定義(つまり再実装)することもできます。 C99またはC11標準の規定と互換性のある動作。

Linuxでは、メモリのオーバーコミットを無効にして valgrind を使用します。 gcc -Wall -Wextra(およびデバッグ時にはおそらく-g)を使用してコンパイルします。少なくともいくつかのいたずらなバグを探すために、-fsanitize=addressを最近のgccまたはclangに渡すことも検討してください。 。)。

ところで、時々ベームの保守的なガベージコレクター は役に立つかもしれません。 (プログラム全体で)mallocの代わりにGC_MALLOCを使用し、free- ingメモリを気にしません。

free()は、実際にメモリをオペレーティングシステムに戻し、プロセスを小さくすることができます。通常、実行できるのは、後でmallocを呼び出すことを許可することですスペースを再利用するため。その間、スペースはmallocによって内部的に使用されるフリーリストの一部としてプログラムに残ります。

2
Samurai Jack