19

最近のすべてのノイズCで、Cでのmallocの使用を最小限に抑える方法がいくつかあり、それは非常に良い方法であると読みました。いつ、どのように、どのような慣行が良いのか、私にはわかりません。だから私の質問は、おそらく経験豊富なCプログラマーが、mallocなしで何かを書くことができる(またはすべきである)いくつかの例を示すことができるかもしれませんが、初心者のCプログラマーにとって本当に非自明な方法は何でしょうか(したがって、初心者は単にmallocを使用すると言いました) ?たぶん、mallocを他の何かに因数分解した経験があります。

PS私が読んだいくつかの投稿は、Quake 3のソースコードとそれがmallocの使用を回避する方法を参照していたので、誰かがこれについて知っているなら、少なくとも知っている限り、私は地震を掘り下げたくないので、そこで何が行われているのかを知ることは興味深いでしょう意図せずにコーディングします。(mallocの使用を避けた場合、mallocの検索では多くの結果が得られないと思いますが、コードベースも個々の例ほど単純ではない可能性があります)

4

6 に答える 6

13

を完全に避けることについてはわかりませんがmalloc、確実に減らすことができます。

基本的な概念はメモリ プールです。これは、多数の小さな割り当てを要求する代わりに、多くのオブジェクトに使用できる割り当て済みの大きなバッファーです。

これは、イベントをキューに送信して別のスレッドで処理するという実際の状況で使用できます。mallocイベント オブジェクトは小さな構造である可能性があり、毎秒何千回も呼び出しを行うことは避ける必要があります。

もちろん、答えはこれらのイベント オブジェクトをプールから引き出すことです。必要に応じて、プール バッファーの一部を使用してリストを作成し、プールに返されたメモリのインデックスをすばやく作成することもできます。これらは一般にフリーリストとして知られています。

データのアライメントがずれているとパフォーマンスに深刻な影響を与える可能性があるため、メモリのアライメントには注意する必要があります。しかし、ちょっとした計算ですべてを処理できます。


これらの概念に惑わされないでください。プールは実際にはそれほど洗練されている必要はありません。このことを考慮:

int ** matrix = malloc( rows * sizeof(int*) );
for( int i = 0; i < rows; i++ ) {
    matrix[i] = malloc( cols * sizeof(int) );
}

私はこれをいつも見ていて、それは私のペットピーブです. これができるのに、なぜそれをするのですか:

int ** matrix = malloc( rows * sizeof(int*) );
matrix[0] = malloc( rows * cols * sizeof(int) );
for( int i = 1; i < rows; i++ ) {
    matrix[i] = matrix[i-1] + cols;
}

そしてもちろん、それはこれに還元されます(ただし、最初の行の潜在的なアライメントの問題に注意してください-明確にするために、ここでは無視しました)

int ** matrix = malloc( rows * sizeof(int*) + rows * cols * sizeof(int) );
matrix[0] = (int*)matrix + rows;
for( int i = 1; i < rows; i++ ) {
    matrix[i] = matrix[i-1] + cols;
}

最後の例の素晴らしい点は、行列を簡単に削除できることです =)

free( matrix );

ああ、行列をゼロにするのも同じくらい簡単です...

memset( matrix[0], 0, rows * cols * sizeof(int) );
于 2013-01-22T13:11:19.933 に答える
5

ローカルスコープで小さな動的サイズの配列が必要なシナリオでは alloca()、スタックから割り当てられ、メモリを明示的に解放する必要がない (関数が戻ると解放される) ものと、可変長配列 ( VLA) :

void meh(int s) {
    float *foo = alloca(s * sizeof(float));
    float frob[s];
} // note: foo and frob are freed upon returning
于 2013-01-22T13:01:50.833 に答える
3

配列、リスト、スタック、ツリーなど、プログラムに必要なデータ構造のすべてのサイズが事前にわかっている場合は、一定数の要素の配列を定義することで、必要なメモリを静的に割り当てることができます。長所: メモリ管理なし、メモリの断片化なし、高速。短所:使用が制限され、メモリが無駄になります。

OSが提供するものの上にカスタムメモリアロケータを実装し、大きなメモリチャンクを一度割り当ててから、標準関数malloc()を呼び出さずに分割することができます。malloc()長所:速い。短所:正しく実装するのは簡単ではありません。

回避するもう 1 つの (そしてかなりひねくれた) 方法は、malloc()ほとんどのデータをメモリではなくファイルに格納することです。長所:ほとんどありません。

プログラムのスタックが十分に大きくなることが確実な場合は、ローカル変数と深い関数呼び出し (または明示的な再帰) を使用して、外出先でデータ用のスペースを割り当てることもできます。長所: メモリ管理なし、簡単、高速。短所:使用が制限されています。

回避する作業中規模プロジェクトの例として、malloc()私のお気に入りのプロジェクトであるSmaller C compilerを提供できます。いくつかの配列を静的に割り当て、再帰関数内に小さなローカル変数を割り当てます。コードはまだ美化されておらず、プログラミング、C、またはコンパイラにかなり慣れていない場合は、小さくて理解しやすいものではないことに注意してください.

于 2013-01-22T13:27:04.513 に答える
3

一部の特定のケースで使用しない主な理由mallocは、おそらく、メモリ割り当てに一般的なフリーサイズのアプローチを採用しているという事実です。

メモリ プールやスラブ割り当てなどの他のアプローチは、既知の割り当てニーズがある場合に利点を提供する可能性があります。

たとえば、アロケーターにとって、割り当てられたオブジェクトが固定サイズであると想定したり、オブジェクトの有効期間が比較的短いと想定したりすると、はるかに有利になります。ジェネリック アロケーターはそのような仮定を行うことができないため、そのようなシナリオでは最適に実行できません。

潜在的な利点には、より凝縮された簿記を持つ特殊なアロケーターによるメモリ フットプリントの減少が含まれます。一般的なアロケーターは、割り当てられたオブジェクトごとに大量のメタデータを保持する可能性が最も高く、オブジェクトのサイズが事前に「わかっている」アロケーターは、おそらくメタデータからそれを省略できます。

また、割り当て速度にも違いが生じる可能性があります。カスタム アロケータを使用すると、おそらく空のスロットをより速く見つけることができます。

これはすべて親戚の話ですが、カスタム割り当てスキームを選択する前に尋ねるべき質問は次のとおりです。

  • 同じサイズの多数のオブジェクトを割り当ておよび割り当て解除する必要がありますか? (スラブ割付)

  • これらのオブジェクトは、個々の呼び出しのオーバーヘッドなしで一度に破棄できますか? (メモリプール)

  • 個別に割り当てられたオブジェクトの論理グループはありますか? (キャッシュ対応割り当て)

肝心なのは、プログラムの割り当てのニーズとパターンを注意深く調べてから、カスタムの割り当てスキームが有益かどうかを判断する必要があるということです。

于 2013-01-22T13:05:06.940 に答える
2

避けるべきいくつかの理由がありますmalloc- 私の頭の中で最大のものは、ボブ・マーリーを言い換えると「no malloc, no free」freeです.

そしてもちろん、NULLメモリを動的に割り当てるときは常にチェックする必要があります。これを回避すると、コードの量とコードの複雑さが軽減されます。

残念ながら、別の方法として、スタックまたはグローバル変数のサイズが不足していることがよくあります。ユーザーに意味のあるエラー メッセージが表示されずにすぐにクラッシュするか (stackoverflow)、グローバル変数のバッファ オーバーフローが発生するためです。グローバル変数の境界をチェックすると、これを回避できます。しかし、それを検出したらどうしますか?多くの選択肢はありません。

もちろん、他の部分は、mallocローカル変数と比較して、への呼び出しがかなり高価になる可能性があることです。これは特に、頻繁に呼び出されるコードの一部である「ホットパス」で malloc/free 呼び出しを実行した場合に当てはまります。小さなメモリ セクションで使用すると、メモリ オーバーヘッドもありますmalloc。Visual Studio での過去の経験からのオーバーヘッドは、約 32 バイトの「ヘッダー」であり、16 または 32 バイトの境界に丸められます。したがって、1 バイトの割り当ては実際には 64 バイトを占めます。17 バイトを割り当てると、64 バイトも使用されます...

もちろん、すべてのエンジニアリング/ソフトウェア設計と同様に、「malloc を使用してはならない」ということではなく、「単純で適切な代替手段がある場合は malloc を避ける」ということです。malloc を回避するためだけに、必要なサイズよりも数倍大きいすべてのグローバル変数を使用するのは間違っていますが、すべてのフレームまたはグラフィックス描画ループのすべてのオブジェクトに対して malloc/free を呼び出すのも同様に間違っています。

Quake のコードは見ていませんが、3DMark 2000 でいくつかのコードに取り組みました [私の名前はまだ製品のクレジットにあると思います]。これは C++ で書かれていますが、レンダリング コードで new/delete を使用することを避けています。ごくわずかな例外を除いて、すべてフレームのセットアップ/ティアダウンで行われます。

于 2013-01-22T13:22:22.547 に答える
2

通常は、より大きなメモリ ブロックを割り当てる方が高速なので、大きなブロックを割り当ててから、そこからメモリ プールを作成することをお勧めします。独自の関数を実装して、メモリを「解放」してプールに戻し、そこからメモリを割り当てます。

于 2013-01-22T13:04:53.337 に答える