24

私は C コードの多くの行をクリーンアップ ラベル/条件付きメモリ割り当ての失敗に費やしました (allocファミリーが を返すことで示されますNULL)。これは、メモリ障害が発生した場合に適切なエラー ステータスにフラグを立て、呼び出し元が「適切なメモリ クリーンアップ」を実行して再試行できるようにするための優れた方法であると教えられました。私は今、この哲学についていくつかの疑問を抱いており、それを解決したいと思っています.

I guess it's possible that a caller could deallocate excessive buffer space or strip relational objects of their data, but I find the caller rarely has the capability (or is at the appropriate level of abstraction) to do so. Also, early-returning from the called function without side effects is often non-trivial.

I also just discovered the Linux OOM killer, which seems to make these efforts totally pointless on my primary development platform.

By default, Linux follows an optimistic memory allocation strategy. This means that when malloc() returns non-NULL there is no guarantee that the memory really is available. This is a really bad bug. In case it turns out that the system is out of memory, one or more processes will be killed by the infamous OOM killer.

I figure there are probably other platforms out there that follow the same principle. Is there something pragmatic that makes checking for OOM conditions worthwhile?

4

11 に答える 11

21

ユーザーまたはシステム管理者がプロセスのメモリ領域を制限 (ulimit を参照) したり、オペレーティング システムがユーザーごとのメモリ割り当て制限をサポートしている場合は、多くのメモリを搭載した最新のコンピューターでもメモリ不足の状態が発生する可能性があります。病理学的なケースでは、断片化により、これがかなり可能性が高くなります。

ただし、最近のプログラムでは動的に割り当てられたメモリの使用が一般的であるため、正当な理由により、メモリ不足エラーの処理が非常に困難になります。この種のエラーのチェックと処理は、あらゆる場所で実行する必要があり、複雑さが高くなります。

いつでもクラッシュできるようにプログラムを設計する方がよいと思います。たとえば、ユーザーが明示的に保存しない場合でも、ユーザーが作成したデータが常にディスクに保存されるようにします。(たとえば、vi -r を参照してください。) このようにして、エラーが発生した場合にプログラムを終了するメモリを割り当てる関数を作成できます。アプリケーションはいつでもクラッシュを処理できるように設計されているため、クラッシュしても問題ありません。ユーザーは驚くでしょうが、(多くの) 作業を失うことはありません。

決して失敗しない割り当て関数は、次のようなものかもしれません (デモンストレーションのみを目的として、テストもコンパイルもされていないコードです)。

/* Callback function so application can do some emergency saving if it wants to. */
static void (*safe_malloc_callback)(int error_number, size_t requested);

void safe_malloc_set_callback(void (*callback)(int, size_t))
{
    safe_malloc_callback = callback;
}

void *safe_malloc(size_t n)
{
    void *p;

    if (n == 0)
        n = 1; /* malloc(0) is not well defined. */
    p = malloc(n);
    if (p == NULL) {
        if (safe_malloc_callback)
            safe_malloc_callback(errno, n);
        exit(EXIT_FAILURE);
    }
    return p;
}

Valerie Aurora の記事Crash-only softwareは参考になるかもしれません。

于 2009-04-18T09:18:03.560 に答える
14

質問の反対側を見てください。mallocメモリを使用すると失敗 し、mallocで検出されない場合、いつ検出れますか?

明らかに、ポインタを逆参照しようとすると。

どのようにそれを検出しますか?または類似のものを取得することによりBus error、mallocの後のどこかで、コアダンプとデバッガーを使用して追跡する必要があります。

一方、あなたは書くことができます

  #define OOM 42 /* just some number */

  /* ... */

  if((ptr=malloc(size))==NULL){
      /* a well-behaved fprintf should NOT malloc, so it can be used
       * in this sort of context
       */
      fprintf(stderr,"OOM at %s: %s\n", __FILE__, __LINE__);
      exit(OOM);
   }

「OOMatparser.c:447」を取得します。

あなたが選ぶ。

アップデート

優雅な帰りについての良い質問。優雅なリターンを保証することの難しさは、一般的に、それを行う方法のパラダイムまたはパターンを実際に設定できないことです。特に、Cでは、結局のところ、派手なアセンブリ言語です。ガベージコレクション環境では、GCを強制することができます。例外のある言語では、例外をスローして、物事をほどくことができます。Cでは、自分でそれを行う必要があるため、それにどれだけの労力を費やすかを決定する必要があります。

ほとんどのプログラムでは、異常終了はあなたができる最善のことです。このスキームでは、(うまくいけば)stderrに関する有用なメッセージ(もちろん、ロガーなどへのメッセージもあります)と、戻りコードとしての既知の値を取得します。

回復時間が短い信頼性の高いプログラムは、回復ブロックのようなものにあなたを押し込みます。そこでは、システムを存続可能な状態に戻そうとするコードを記述します。これらは素晴らしいですが、複雑です。私がリンクした論文はそれらについて詳細に話している。

途中で、より複雑なメモリ管理スキームを思い付くことができます。たとえば、動的メモリの独自のプールを管理することです。結局のところ、他の誰かがmallocを作成できる場合は、そうすることもできます。

しかし、確実に戻って周囲のプログラムを続行できるように十分にクリーンアップするための一般的なパターン(とにかく私が知っている)はありません。

于 2009-04-18T10:05:01.217 に答える
8

プラットフォームに関係なく (おそらく組み込みシステムを除くNULL)、手動でクリーンアップをまったく (または多く) 実行せずに、チェックして終了することをお勧めします。

メモリ不足は単純なエラーではありません。これは、今日のシステムにとって大惨事です。

The Practice of Programming (Brian W. Kernighan および Rob Pike、1999 年)という本はemalloc()、メモリが残っていない場合にエラー メッセージを表示して終了するような関数を定義しています。

于 2009-04-18T08:56:05.703 に答える
6

それはあなたが書いているものに依存します。汎用ライブラリですか?もしそうなら、特にそれが el-cheapo システムまたは組み込みデバイスで使用されると予想するのが合理的である場合は、メモリ不足にできるだけ適切に対処する必要があります。

これを考慮してください: プログラマーがあなたのライブラリーを使用しています。彼のプログラムにはバグ (おそらく初期化されていない変数) があり、愚かな引数がコードに渡され、その結果、3.6 GB のメモリ ブロックが 1 つ割り当てられます。明らかmalloc()に NULL を返します。ライブラリ コードのどこかで生成された原因不明の segfault と、エラーを示す戻り値のどちらがよいでしょうか?

コード全体でエラー チェックが行われるのを避けるための 1 つの方法は、最初に妥当な量のメモリを割り当て、必要に応じてサブ割り当てすることです。

Linux OOM キラーに関しては、主要なディストリビューションではこの動作がデフォルトで無効になっていると聞きました。有効になっている場合でも、誤解しないでください。NULL を返すmalloc() 可能性があり、プログラムの合計メモリ使用量が 4GiB (32 ビット システム) を超える場合は確実に返されます。言い換えれば、malloc()RAM/スワップ空間を実際に確保していなくても、アドレス空間の一部を予約します

于 2009-04-18T10:09:58.363 に答える
4

実験をお勧めします - メモリを解放せずに割り当て続け、割り当てが失敗したときに小さな (修正された) メッセージを出力する小さなプログラムを作成します。このプログラムを実行すると、システムにどのような影響が見られますか? メッセージは印刷されますか?

システムが正常に動作し、エラーが表示される時点まで応答している場合は、確認する価値があります。OTOHは、メッセージが表示される前にシステムが遅くなり、応答しなくなり、最終的に使用できなくなった場合(表示される場合)、IIはノーと言います。チェックする価値はありません.

重要: このテストを実行する前に、重要な作業をすべて保存してください。本番サーバーでは実行しないでください。

Linux OOM の動作について - これは実際には望ましいことであり、ほとんどの OS が動作する方法です。メモリを malloc() する場合、OS から直接取得するのではなく、C ランタイム ライブラリから取得することを認識することが重要です。これは通常、事前に (または最初の要求で) OS に大量のメモリを要求し、それを malloc/free インターフェイスを介して管理します。多くのプログラムは動的メモリをまったく使用しないため、OS が「実際の」メモリを C ランタイムに渡すことは望ましくありません。代わりに、malloc 呼び出しを行うときに実際にコミットされる未コミットの vM を渡します。

于 2009-04-18T09:17:43.013 に答える
2

今日のコンピューターと通常搭載されている RAM の量では、メモリ割り当てエラーをあらゆる場所でチェックするのは、おそらく詳細すぎるでしょう。これまで見てきたように、何を解放するかについて合理的な決定を下すことは、多くの場合、困難または不可能です。プロセスがますます多くのメモリを割り当てるにつれて、OS はそれに応じてディスク バッファに使用できるメモリの量を減らします。あるしきい値を下回ると、OS はメモリをディスクにページングし始めます。(メモリ管理には多くの要因があるため、これは単純化したものです。)

OS がメモリのページングを開始すると、システム全体が次第に遅くなり、アプリケーションが実際に malloc から NULL を確認するまでには (もしあったとしても) かなりの時間がかかるでしょう。

今日のシステムで利用可能なメモリ量が非常に多いため、「メモリ不足」エラーは、コードのバグが任意の量のメモリを割り当てようとしたことを意味する可能性が高くなります。その場合、プロセスの一部でいくら解放して再試行しても問題は解決しません。

于 2009-04-18T08:59:49.017 に答える
1

上手。すべては状況に依存します。

初めに。あなたがあなたの必要性のために記憶が不十分であるとあなたが検出したならば-あなたは何をしますか?最も一般的な使用法は次のとおりです。

if (ptr == NULL) {
    fprintf(log /* stderr or anything */, "Cannot allocate memory");
    exit(2);
}

上手。mallocを使用しない場合でも、バッファーを割り当てる場合があります。それがGUIアプリケーションである場合、さらに悪いことです-ユーザーがそれを見つける可能性は低いです。ユーザーがコンソールからアプリケーションを実行してエラーをチェックするのに「十分に賢い」場合、おそらく何かが彼のメモリ全体を食べたことがわかります。Ok。では、ダイアログを表示するのでしょうか?ただし、ダイアログを表示するとリソースが消費される可能性があります。通常はそうなります。

第二に、なぜOOMに関する情報が必要なのですか?これは2つの場合に発生します。

  1. 他のソフトウェアはバグがあります。あなたはそれで何もできません
  2. あなたのプログラムはバグがあります。このような場合、ユーザーに通知する可能性が低いのは8つのGUIプログラムです(99%のユーザーがメッセージを読んでおらず、詳細なしでソフトウェアがクラッシュしたと言うことは言うまでもありません)。そうでない場合、ユーザーはとにかくそれを見つける可能性があります(システムモニターを監視するか、より特殊なソフトウェアを使用します)。
  3. 一部のキャッシュなどを解放するには、システムをチェックインする必要がありますが、機能しない可能性があることに注意してください。自分のsbrk/mmap/etcのみを処理できます。呼び出しとLinuxではとにかくOOMを取得します
于 2009-04-18T20:01:57.007 に答える
1

自分にとってどちらが良いか悪いかを比較検討する必要があります: すべての作業を OOM のチェックに費やすか、予期しないタイミングでプログラムを失敗させるか

于 2009-04-18T08:55:57.717 に答える
0

ソフトウェアの設計を誤ると、OOMの状態を確認して適切なアクションを実行するのが難しい場合があります。このような状況を実際にチェックする必要があるかどうかは、入手したいソフトウェアの信頼性によって異なります。

たとえば、VirtualBoxハイパーバイザーはメモリ不足エラーを検出し、仮想マシンを正常に一時停止して、ユーザーが一部のアプリケーションを閉じてメモリを解放できるようにします。私はWindowsでそのような振る舞いを観察しました。実際、VirtualBoxのほとんどすべての呼び出しには、戻り値として成功インジケーターがありVERR_NO_MEMORY、メモリ割り当てが失敗したことを示すために戻ることができます。これにより、いくつかの追加チェックが導入されますが、この場合はそれだけの価値があります。

于 2009-04-18T09:32:53.897 に答える
0

はい、一貫して練習を続ければ、そうなると思います。C で書かれた大規模なプログラムの場合、これには手作業が必要になる可能性があるため、これは非現実的かもしれませんが、より現代的な言語では、メモリ不足の状態で例外がスローされるため、この作業のほとんどが自動的に行われます。

これを一貫して行うことの利点は、バッファ オーバーランにつながるメモリ不足の状態によってプログラムが未定義状態にならないことです (これにより、明らかに関数の早期終了による未定義状態の可能性が残りますが、これは別のクラスのバグ)。そうすることで、プログラムは一貫してエラー状態を処理できます。または、障害が重大なものである場合は、適切な方法で終了することを決定できます。

于 2009-04-18T08:57:18.070 に答える