free
を呼び出すことによって作成されていないオブジェクトを取り除くために使用するのは悲惨なことだとどこかで読みましたがmalloc
、これは本当ですか? なぜ?
7 に答える
これは未定義の動作です。絶対に試さないでください。
free()
自動変数を試してみるとどうなるか見てみましょう。ヒープマネージャは、メモリブロックの所有権を取得する方法を推測する必要があります。そのためには、割り当てられたすべてのブロックを一覧表示する別の構造を使用する必要があります。これは非常に遅く、めったに使用されないか、必要なデータがブロックの先頭近くにあることを期待します。
後者は非常に頻繁に使用され、これが私がどのように機能することになっているのかです。malloc()を呼び出すと、ヒープマネージャーは少し大きいブロックを割り当て、サービスデータを最初に格納し、オフセットポインターを返します。のようなSmth:
void* malloc( size_t size )
{
void* block = tryAlloc( size + sizeof( size_t) );
if( block == 0 ) {
return 0;
}
// the following is for illustration, more service data is usually written
*((size_t*)block) = size;
return (size_t*)block + 1;
}
次にfree()
、渡されたポインタをオフセットしてそのデータにアクセスしようとしますが、ポインタが自動変数を指している場合は、サービスデータの検索が必要な場所にデータが配置されます。したがって、未定義の動作。多くの場合free()
、ヒープマネージャがブロックの所有権を取得するためにサービスデータが変更されます。そのため、渡されたポインタが自動変数に対するものである場合、無関係なメモリが変更され、そこから読み取られます。
実装は異なる場合がありますが、特定の仮定を行うことは絶対にしないでください。ファミリ関数free()
によって返されたアドレスのみを呼び出します。malloc()
標準では、これは「未定義の動作」、つまり「何でも起こり得る」です。しかし、それは通常悪いことです。
実際には:free
'ポインタを使用するということは、ヒープを変更することを意味します。Cランタイムは、渡されたポインターがヒープからのものであるかどうかを事実上検証しません。これは、時間またはメモリのいずれかでコストがかかることになります。これらの2つのファクトイドを組み合わせると、「free(non-malloced-ptr)はどこかに何かを書き込む」という結果が得られます。結果は、背後で変更された「あなたの」データ、アクセス違反、または次のような重要なランタイム構造の破棄である可能性があります。スタック上のリターンアドレス。
例:悲惨なシナリオ:
ヒープは、空きブロックの単純なリストとして実装されます。mallocはリストから適切なブロックを削除することを意味し、freeはそれをリストに再度追加することを意味します。(些細な実装の場合は一般的)
スタック上のローカル変数へのポインタをfree()します。変更が無関係なスタックスペースに入るので、あなたは「幸運」です。ただし、スタックの一部が無料リストに追加されました。
アロケータの設計と割り当てパターンのため、mallocがこのブロックを返す可能性はほとんどありません。後で、プログラムの完全に無関係な部分で、実際にこのブロックをmallocの結果として取得し、それに書き込むとスタックの一部のローカル変数が破棄され、重要なポインターを返すとゴミが含まれ、アプリがクラッシュします。症状、再現、場所は実際の原因とはまったく関係ありません。
それをデバッグします。
これは未定義の動作です。そして論理的には、動作が定義されていない場合、何が起こったのか、プログラムがまだ適切に動作しているかどうかを確認できません。
ここで、これは「未定義の動作」であると指摘する人もいます。さらに進んで、一部の実装では、これによりプログラムがクラッシュするか、データが破損する可能性があると言います。これは、「malloc」と「free」の実装方法と関係があります。
malloc / freeを実装する1つの可能な方法は、割り当てられた各リージョンの前に小さなヘッダーを配置することです。mallocされたリージョンでは、そのヘッダーにリージョンのサイズが含まれます。リージョンが解放されると、そのヘッダーがチェックされ、リージョンが適切なフリーリストに追加されます。これがあなたに起こった場合、これは悪いニュースです。たとえば、スタックに割り当てられたオブジェクトを解放すると、突然スタックの一部がフリーリストに含まれます。次に、mallocは将来の呼び出しに応答してその領域を返す可能性があり、スタック全体にデータを書き留めます。もう1つの可能性は、文字列定数を解放することです。その文字列定数が読み取り専用メモリにある場合(多くの場合)、この架空の実装により、後のmallocの後、またはfreeがオブジェクトをフリーリストに追加したときに、セグメンテーション違反が発生してクラッシュします。
これは私が話している架空の実装ですが、想像力を使って、それが非常に、非常に間違っている可能性があることを確認できます。一部の実装は非常に堅牢であり、この正確なタイプのユーザーエラーに対して脆弱ではありません。一部の実装では、環境変数を設定してこれらのタイプのエラーを診断することもできます。Valgrindやその他のツールもこれらのエラーを検出します。
厳密に言えば、これは真実ではありません。calloc()とrealloc()も、free()の有効なオブジェクトソースです。;)
未定義の動作が意味するものを見てください。準拠しているmalloc()
ホストされたC 実装では、標準に基づいて構築されています。標準では、返されなかったヒープ ブロック(またはそれをラップするものなど) を呼び出す動作は未定義であるとされています。free()
free()
malloc()
calloc()
つまり、自分で必要な変更を加えれば、やりたいことは何でもできるということですfree()
。on ブロックの動作を一貫性があり、場合によっては有用なものにすることで、標準を破ることはありません。free()
malloc()
実際、(それ自体で)この動作を定義するプラットフォームが存在する可能性があります。私は何も知りませんが、いくつかあるかもしれません。イベントをログに記録する際に、より適切に失敗させる可能性のあるガベージ コレクション/ログの malloc() 実装がいくつかあります。しかし、それは実装であり、標準で定義された動作ではありません。
未定義とは、定義された動作を壊さずに自分で実装しない限り、一貫した動作を期待しないことを意味します。最後に、定義済みの実装は、必ずしもホスト システムによって定義されているとは限りません。多くのプログラムはuclibcに対してリンク (および出荷) します。その場合、実装は自己完結型で一貫性があり、移植可能です。
/の実装が割り当てられたメモリ ブロックのリストを保持することは確かに可能であり、ユーザーがこのリストにないブロックを解放しようとしても何もしません。malloc
free
ただし、標準ではこれは要件ではないと述べているため、ほとんどの実装では、解放されるすべてのポインターが有効なものとして扱われます。