web-dev-qa-db-ja.com

Cの「free」が解放されるバイト数を取得しないのはなぜですか?

明確にするために:mallocfreeがCライブラリに実装されていることを知っています。通常、これはOSからメモリのチャンクを割り当て、独自の管理を行って小さなメモリを分配します。アプリケーションに割り当てられ、割り当てられたバイト数を追跡​​します。この質問は、 無料はどのくらい解放するかを知る方法 ではありません。

むしろ、最初にfreeがこのように作成された理由を知りたいです。低レベルの言語であるため、Cプログラマーに割り当てられたメモリだけでなく、どのくらいの量を追跡するように依頼することは完全に合理的だと思います(実際、通常、バイト数を追跡​​することになりますとにかくmalloced)。また、バイト数をfreeに明示的に指定すると、パフォーマンスの最適化が可能になる場合があります。異なる割り当てサイズの個別のプールを持つアロケーターは、入力引数を見るだけでどのプールから解放するかを決定でき、全体的なスペースのオーバーヘッドが少なくなります。

要するに、なぜmallocfreeは、割り当てられたバイト数を内部で追跡する必要があるように作成されたのですか?それは単なる歴史的な事故ですか?

小さな編集:少数の人々が「割り当てた量とは異なる量を解放した場合」などのポイントを提供しました。私の想像したAPIは、割り当てられたバイト数を正確に解放するために必要なAPIでした。多かれ少なかれ解放することは、単にUBまたは実装定義です。しかし、他の可能性についての議論を思いとどまらせたくありません。

82
jaymmer

1つの引数free(void *)(Unix V7で導入)には、ここで言及したことのない、以前の2つの引数mfree(void *, size_t)よりも大きな利点があります。1つの引数freeは、 otherヒープメモリで動作するAPI。たとえば、freeがメモリブロックのサイズを必要とする場合、strdupは1つ(ポインター)ではなく2つの値(ポインター+サイズ)を何らかの方法で返す必要があり、Cは複数値の戻り値を単一値の戻り値よりもはるかに扱いにくくします。 char *strdup(char *)の代わりに、char *strdup(char *, size_t *)またはstruct CharPWithSize { char *val; size_t size}; CharPWithSize strdup(char *)を記述する必要があります。 (今日では、2番目のオプションはかなり魅力的に見えます。なぜなら、NULで終わる文字列は 「コンピューティングの歴史の中で最も壊滅的な設計バグ」 であることがわかっているからです。しかし、それは後知恵です。70年代、Cの能力文字列を単純な_char *_として処理することは、実際には PascalやALGOLなどの競合他社に対する優位性を定義する と見なされていました。さらに、この問題に苦しんでいるのはstrdupだけではありません。ヒープメモリを割り当てるシステム定義またはユーザー定義の関数。

初期のUnixデザイナーは非常に賢い人であり、freemfreeよりも優れている理由はたくさんあります。そのため、基本的にはこの問題に対する答えはこれに気づき、システムを設計したことだと思います。彼らがその決定を下した瞬間に彼らの頭の中で起こっていたことの直接的な記録を見つけることはできないと思う。しかし、想像できます。

2つの引数mfreeを使用して、V6 Unixで実行するアプリケーションをCで作成していると仮定します。これまで大丈夫でしたが、これらのポインターサイズを追跡することは、プログラム より野心的になる であり、ヒープ割り当て変数をますます使用する必要があるため、ますます面倒になっています。しかし、あなたは素晴らしいアイデアを持っています:これらの_size_t_ sを常にコピーする代わりに、割り当てられたメモリ内に直接サイズを隠しておくいくつかのユーティリティ関数を書くことができます:

_void *my_alloc(size_t size) {
    void *block = malloc(sizeof(size) + size);
    *(size_t *)block = size;
    return (void *) ((size_t *)block + 1);
}
void my_free(void *block) {
    block = (size_t *)block - 1;
    mfree(block, *(size_t *)block);
}
_

そして、これらの新しい関数を使用して書くコードが多ければ多いほど、それらは素晴らしいように見えます。彼らはあなたのコードを書きやすくするだけでなく、alsoあなたのコードを作成しますfaster-しばしば一緒に行かない二つのこと!これらの_size_t_ sをあちこちに渡す前に、コピーのためのCPUオーバーヘッドが追加され、より多くのレジスタを追加する必要がありました(特に追加の関数引数のため)、メモリの浪費(ネストされた関数のため)多くの場合、呼び出しにより_size_t_の複数のコピーが異なるスタックフレームに格納されます)。新しいシステムでは、_size_t_を格納するためにメモリを消費する必要がありますが、一度だけであり、どこにもコピーされません。これらは小さな効率のように見えるかもしれませんが、256 KiBのRAMを搭載したハイエンドマシンについて話していることに注意してください。

これはあなたを幸せにします!したがって、次のUnixリリースに取り組んでいるひげを生やした男性とあなたのクールなトリックを共有しますが、それは彼らを幸せにせず、悲しくさせます。ご覧のとおり、彼らはstrdupのような多数の新しいユーティリティ関数を追加する過程にあり、それらの新しい関数はすべて面倒なポインターを使用しているため、クールなトリックを使用している人は新しい関数を使用できないことを認識していますサイズAPI。システムバージョンを使用する代わりに、作成するすべてのプログラムでstrdup(char *)関数を自分で書き直さなければならないことに気付くので、それも悲しみになります。

ちょっと待って!これは1977年であり、下位互換性はさらに5年間発明されません。それに、実際には誰も真剣ではありませんsesこのあいまいな "Unix"の色のない名前。 K&Rの初版は現在出版社に向かっていますが、それは問題ありません。最初のページで、「Cは文字列などの複合オブジェクトを直接処理する操作を提供しません...ヒープはありません」 ...」。歴史のこの時点では、_string.h_とmallocはベンダー拡張(!)です。したがって、ひげを生やした男#1を提案します。なぜトリッキーなアロケーターをofficialアロケーターとして宣言しないのですか?

数日後、Bearded Man#2は新しいAPIを見て、ちょっと待ってください、これは以前よりも優れていますが、サイズを保存するために割り当てごとにWord全体を費やしています。彼はこれを冒blの次のことだと考えています。他の誰もが彼を狂ったように見ます、あなたは他に何ができるのですか?その夜、彼は遅くまで滞在し、サイズをまったく保存しない新しいアロケーターを発明しますが、代わりにポインター値で黒魔法のビットシフトを実行することでその場で推測し、新しいAPIを所定の場所に保持しながらスワップします。新しいAPIは、誰もスイッチに気付かないことを意味しますが、翌朝にコンパイラが使用するRAMが10%少ないことに気付きます。

そして今、みんなが幸せです:あなたはあなたのより簡単で高速なコードを手に入れ、Bearded Man#1は人々が実際に使用するニースのシンプルなstrdupを作成し、Bearded Man#2-彼は少しの間彼の維持を得ていると確信しています- quinesをいじる に戻ります。それを出荷!

または少なくとも、それがcouldが起こった方法です。

94

実際、古代のUnixカーネルメモリアロケーターでは、mfree()size引数を取りました。 malloc()およびmfree()は、空きブロックのアドレスとサイズに関する情報を含む2つの配列(コアメモリ用、スワップ用)を保持していました。

Unix V6まではユーザー空間アロケーターはありませんでした(プログラムはsbrk()を使用するだけです)。 Unix V6では、iolibにはalloc(size)およびfree()呼び出しを伴うアロケーターが含まれており、サイズ引数を取りませんでした。各メモリブロックには、そのサイズと次のブロックへのポインタが先行していました。ポインタは、空きリストを調べるときに空きブロックでのみ使用され、使用中のブロックのブロックメモリとして再利用されました。

Unix 32VおよびUnix V7では、これは新しいmalloc()およびfree()実装に置き換えられました。free()size引数を取りませんでした。実装は循環リストであり、各チャンクの前には、次のチャンクへのポインターと「ビジー」(割り当て済み)ビットを含むWordがありました。そのため、malloc()/free()は明示的なサイズを追跡することさえしませんでした。

14
ninjalj

CはC++ほど「抽象的」ではないかもしれませんが、それでもアセンブリを抽象化することを意図しています。そのために、最下位レベルの詳細が方程式から取り出されます。これにより、ほとんどの場合、すべてのCプログラムを移植できなくする、位置合わせとパディングを気にする必要がなくなります。

要するに、これが抽象化を記述する全体のポイントです

Cのfreeが解放されるバイト数を取得しないのはなぜですか?

必要がないため。この情報は、malloc/freeによって実行される内部管理ですでに利用可能です。

次に、2つの考慮事項を示します(この決定に寄与した場合と寄与していない場合があります)。

  • 関数が必要のないパラメーターを受け取るのはなぜですか?

    (これにより、動的メモリに依存するallクライアントコードが実質的に複雑になり、アプリケーションに不要な冗長性が追加されます)。ポインターの割り当てを追跡することは、すでに困難な問題です。メモリ割り当てと関連するサイズを追跡すると、クライアントコードの複雑さが不必要に増加します。

  • これらの場合、変更されたfree関数は何をしますか?

    void * p = malloc(20);
    free(p, 25); // (1) wrong size provided by client code
    free(NULL, 10); // (2) generic argument mismatch
    

    not not free(メモリリークの原因?) 2番目のパラメーターを無視しますか? exitを呼び出してアプリケーションを停止しますか?これを実装すると、おそらく必要のない機能のために、アプリケーションに余分な障害点が追加されます(必要な場合は、以下の最後の点、「アプリケーションレベルでのソリューションの実装」を参照してください)。

むしろ、そもそもなぜこのようにして無料になったのかを知りたい。

これが「適切な」方法だからです。 APIは、その操作を実行するために必要な引数を必要とし、それ以下を必要とします。

また、空きバイト数を明示的に指定すると、パフォーマンスの最適化が可能になる場合もあります。異なる割り当てサイズの個別のプールを持つアロケーターは、入力引数を見るだけでどのプールから解放するかを決定でき、全体的なスペースのオーバーヘッドが少なくなります。

それを実装する適切な方法は次のとおりです。

  • (システムレベルで)mallocの実装内-ライブラリの実装者がmallocを記述して、受信したサイズに基づいてさまざまな戦略を内部的に使用することを妨げるものは何もありません。

  • (アプリケーションレベルで)独自のAPI内でmallocとfreeをラップし、代わりにそれらを使用します(アプリケーション内の必要なすべての場所)。

9
utnapistim

頭に浮かぶ5つの理由:

  1. 便利です。プログラマからオーバーヘッドの負荷をすべて取り除き、非常に困難なクラスのエラーを回避します。

  2. ブロックの一部を解放する可能性を開きます。しかし、メモリマネージャは通常、追跡情報を持ちたいので、これが何を意味するのか明確ではありませんか?

  3. OrbitのLightness Racesは、パディングと位置合わせにスポットを当てています。メモリ管理の性質により、割り当てられた実際サイズは、要求したサイズとはかなり異なる可能性があります。つまり、サイズと場所を必要とするfreeは、割り当てられた実際のサイズを返すために、mallocも変更する必要があります。

  4. とにかく、サイズを渡すことに実際の利点があるかどうかは明らかではありません。典型的なメモリマネージャは、メモリのチャンクごとにサイズを含む4〜16バイトのヘッダーを持っています。このチャンクヘッダーは、割り当てられたメモリと割り当てられていないメモリで共通であり、隣接するチャンクが解放されると、まとめて折りたたむことができます。呼び出し元に空きメモリを保存させる場合、割り当てられたメモリに個別のサイズフィールドを持たないことで、チャンクごとにおそらく4バイトを解放できますが、呼び出し側はそれをどこかに格納する必要があるため、おそらくサイズフィールドは取得されません。しかし今では、情報はヘッダーのチャンクに予想どおりに配置されるのではなく、メモリに散らばっており、とにかく運用効率が低くなる可能性があります。

  5. wasより効率的であっても、プログラムがメモリを解放するのに大量の時間を費やすことは根本的にありそうにないとにかくので、利益はわずかです。

ちなみに、さまざまなサイズのアイテムの個別のアロケーターについての考えは、この情報がなくても簡単に実装できます(アドレスを使用して、割り当てが発生した場所を判別できます)。これは、C++で日常的に行われます。

後で追加

別の答えは、かなりばかげて、 std :: allocatorfreeがこのように機能することを証明するものですが、実際にはfreeはこの方法では機能しません。 malloc/freeの動作とstd :: allocatorの動作には2つの重要な違いがあります。まず、mallocfreeはユーザー向けです。これらは一般的なプログラマーが使用できるように設計されていますが、std::allocatorは、標準ライブラリへの専門的なメモリ割り当てを提供するように設計されています。これは、私の最初のポイントが重要でない場合、または重要でない場合の良い例です。それはライブラリであるため、追跡サイズの複雑さを処理する難しさはとにかくユーザーから隠されています。

第二に、std :: allocator 常に同じサイズのアイテムで動作しますこれは、最初に渡された要素の数を使用して空き領域を決定できることを意味します。これがfree自体と異なる理由は例示です。 std::allocator割り当てられるアイテムは、常に同じ、既知のサイズで、常に同じ種類のアイテムであるため、常に同じ種類の位置合わせ要件があります。つまり、アロケーターは、これらのアイテムの配列を開始時に単純に割り当て、必要に応じてそれらを削除するように特化できることを意味します。 freeでこれを行うことはできません。返される最適なサイズが要求されたサイズであることを保証する方法がないため、代わりに呼び出し側が要求するよりも大きなブロックを返す方がはるかに効率的です*したがって、eitherユーザーまたはマネージャーは、実際に許可されたexactサイズを追跡する必要があります。この種の実装の詳細をユーザーに渡すことは、呼び出し側に利益をもたらさない不必要な頭痛の種です。

-*この点を理解しにくい人がまだいる場合は、これを考慮してください。典型的なメモリアロケータは、メモリブロックの先頭に少量の追跡情報を追加し、そこからポインタオフセットを返します。ここに格納される情報には、通常、たとえば次の空きブロックへのポインターが含まれます。ヘッダーが4バイトの長さ(実際のほとんどのライブラリよりも実際に小さい)であり、サイズが含まれていない場合、ユーザーが16バイトブロックを要求したときに20バイトの空きブロックがあると想像してみましょう。システムは16バイトブロックを返しますが、mallocが呼び出されるたびに時間を浪費することは決してありえない4バイトのフラグメントを残します。代わりに、マネージャーが単に20バイトブロックを返す場合、これらの乱雑なフラグメントが蓄積するのを防ぎ、利用可能なメモリをよりきれいに割り当てることができます。しかし、システムがサイズ自体を追跡せずにこれを正しく行う場合、ユーザーは、単一の割り当てごとに、メモリの量を追跡する必要があります実際に無料で戻す場合。同じ引数が、目的の境界に一致しない型/割り当てのパディングにも適用されます。したがって、せいぜいfreeがサイズを取る必要があるのは、(a)メモリアロケータが実際に割り当てられたサイズと一致するために渡されたサイズに依存できないため、完全に役に立たないか、(b)ユーザーが賢明なメモリマネージャで簡単に処理できるrealサイズの追跡作業を行います。

9
Jack Aidley

私はこれを答えとして投稿していますが、それはあなたが望んでいるからではなく、もっともらしい正しいものだと信じているからです:

おそらく当初は便利だと思われ、その後改善することはできませんでした。
おそらく説得力のある理由はありません。(ただし、間違っていると示された場合は喜んで削除します。)

そこにwould可能であれば利点があります。サイズが事前にわかっている単一の大きなメモリを割り当ててから、一度に少しずつ解放することができます。小さなチャンクを繰り返し割り当てたり解放したりするのとは対照的ですメモリの。現在、このようなタスクは不可能です。


many(many1!)サイズを渡すことはばかげていると思うあなたの:

std::allocator<T>::deallocate method?

void deallocate(pointer p, size_type n);

nが指す領域内のすべてのTpオブジェクトは、この呼び出しの前に破棄されます。
nは、この変数を取得するためにallocateに渡された値と一致します。

この設計上の決定を分析するのに、かなり「興味深い」時間があると思います。


はどうかと言うと operator delete、2013年の N3778 提案(「C++ Sized Deallocation」)もこれを修正することを目的としていることがわかりました。


1元の質問の下にあるコメントを見て、「サイズの要求はfree呼び出しにはまったく役に立たない」などの性急なアサーションを行った人数を確認してください(---) sizeパラメーター。

5
Mehrdad

mallocとfreeは、それぞれの「malloc」が1つの「free」と一致するように、手をつないで行きます。したがって、以前の「malloc」に一致する「free」は、そのmallocによって割り当てられたメモリの量を単に解放するだけで十分です-これは、99%のケースで理にかなっている多数のユースケースです。世界中のすべてのプログラマーによるmalloc/freeの使用がすべてあり、プログラマーがmallocに割り当てられた量を追跡し、同じものを解放することを覚えている必要がある場合、すべてのメモリエラーを想像してください。あなたが話しているシナリオは、実際には、ある種のメモリ管理の実装で複数のmalloc/freeを使用しているはずです。

2
Marius George

割り当てのサイズを追跡しないアロケーターがどのように機能するかわかりません。これを行わなかった場合、将来のmalloc要求を満たすためにどのメモリが利用可能かをどのように知るのでしょうか?使用可能なメモリブロックがどこにあるかを示すために、少なくともアドレスと長さを含む何らかのデータ構造を保存する必要があります。 (そしてもちろん、空きスペースのリストを保存することは、割り当てられたスペースのリストを保存することと同等です)。

1
M.M

この方法で(場合によっては)サイズ情報を手動で追跡する必要がなく、プログラマーのエラーが発生しにくいため、非常に便利だからです。

さらに、reallocにはこのブックキーピング情報が必要になります。これには、割り当てサイズ以外の情報も含まれると予想されます。つまり、機能するメカニズムを実装定義にすることができます。

あなたが提案する方法でいくらか動作する独自のアロケータを書くことができますが、これは通常、特定の場合(潜在的に大規模なパフォーマンスの向上を伴う)に似たような方法でプールアロケータのc ++で行われますが、これは一般的に演算子の観点で実装されますプールブロックを割り当てるための新しい。

1
Pete

必要なのは、以前に割り当てたメモリを解放するために使用するポインターだけです。バイト数はオペレーティングシステムによって管理されているため、心配する必要はありません。 free()が返す割り当てられたバイト数を取得する必要はありません。実行中のプログラムによって割り当てられたバイト/位置の数を手動でカウントする方法をお勧めします。

Linuxで作業していて、mallocが割り当てたバイト/位置の量を知りたい場合は、mallocを1回またはn回使用して、取得したポインターを出力する単純なプログラムを作成できます。さらに、プログラムを数秒間スリープ状態にする必要があります(次の操作を行うのに十分です)。その後、そのプログラムを実行し、そのPIDを探し、cd/proc/process_PIDを書き込み、「cat maps」と入力します。出力には、特定の行に、ヒープメモリ領域(メモリを動的に割り当てているメモリ領域)の開始メモリアドレスと最終メモリアドレスの両方が表示されます。割り当てられているこれらのメモリ領域へのポインタを出力すると割り当てたメモリ量を推測できます。

それが役に立てば幸い!

0
user3416290

なぜそれが必要ですか? malloc()およびfree()は、意図的に非常に単純なメモリ管理プリミティブであり、Cでの高レベルのメモリ管理は、主に開発者次第です。 T

さらに、realloc()は既にそれを行っています-realloc()で割り当てを減らすと、データは移動されず、返されるポインターは元のポインターと同じになります。

一般に、標準ライブラリ全体では、アプリケーションのニーズに合わせてより複雑な機能を構築できる単純なプリミティブで構成されています。 「なぜ標準ライブラリがXを行わないのか」という形式の質問に対する答えは、プログラマが考えるすべてのことを実行できないため(プログラマが何をするのか)、ほとんど実行しないことを選択するためです。サードパーティのライブラリを使用します。より柔軟なメモリ管理を含む、より広範な標準ライブラリが必要な場合は、C++が答えかもしれません。

質問C++とCにタグを付けました。C++を使用している場合は、いずれの場合もmalloc/freeはほとんど使用しないでください。新規/削除を除き、STLコンテナークラスはメモリを自動的に管理します。さまざまなコンテナの性質に特に適している。

0
Clifford