20

Linux から Windows に C プロジェクトを移植しています。Linux では完全に安定しています。Windows では、ほとんどの場合問題なく動作しますが、セグメンテーション エラーが発生することがあります。

Microsoft Visual Studio 2010 を使用してコンパイルとデバッグを行っていますが、malloc 呼び出しがメモリを割り当てず、NULL を返すことがあるようです。マシンには空きメモリがあります。すでにそのコードを何千回も通過していますが、それでも別の場所で発生しています。

私が言ったように、それはいつも同じ場所で起こるわけではありません。ランダムエラーのようです。

Linux よりも Windows で注意しなければならないことはありますか? 私は何が間違っている可能性がありますか?

4

4 に答える 4

33

malloc()メモリ要求を処理できない場合、NULL の無効なポインターを返します。ほとんどの場合、C メモリ割り当てルーチンは、オペレーティング システムへの呼び出しで使用可能なメモリのリストまたはヒープを管理し、malloc()呼び出しが行われ、要求を満たすブロックがリストまたはヒープにない場合に、追加のメモリ チャンクを割り当てます。

したがって、malloc()失敗の最初のケースは、(1) C ランタイムのリストまたはヒープに使用可能なメモリ ブロックがなく、(2) C ランタイム メモリ管理がより多くのメモリを要求したため、メモリ要求を満たすことができない場合です。要求が拒否されました。

これは、ポインター割り当て戦略に関する記事です。

このフォーラムの記事では、メモリの断片化による malloc の失敗の例を示しています。

失敗するもう 1 つの理由malloc()は、おそらく、割り当てられたメモリ領域が、割り当てられたメモリのサイズよりも大きいオブジェクトに使用されたバッファ オーバーフローが原因で、メモリ管理データ構造が破損したためです。のバージョンが異なれば、メモリ管理とが呼び出されたmalloc()ときに提供するメモリの量を決定するための異なる戦略を使用できます。malloc()たとえば、 amalloc()は、要求された正確なバイト数を提供する場合もあれば、メモリ境界内に割り当てられたブロックに収まるように、またはメモリ管理を容易にするために、要求したよりも多くのバイト数を提供する場合もあります。

最新のオペレーティング システムと仮想メモリでは、非常に大きなメモリ常駐ストレージを使用していない限り、メモリ不足になることはかなり困難です。ただし、以下のコメントでユーザー Yeow_Meng が言及しているように、割り当てるサイズを決定するために算術を行っていて、結果が負の数である場合、割り当てられるメモリmalloc()量の引数が無署名。

一部のデータに必要なスペースの量を決定するためにポインター演算を行うと、負のサイズの問題が発生する可能性があります。この種のエラーは、予期しないテキストに対して行われるテキスト解析で一般的です。たとえば、次のコードは非常に大きなmalloc()リクエストになります。

char pathText[64] = "./dir/prefix";  // a buffer of text with path using dot (.) for current dir
char *pFile = strrchr (pathText, '/');  // find last slash where the file name begins
char *pExt = strrchr (pathText, '.');    // looking for file extension 

// at this point the programmer expected that
//   - pFile points to the last slash in the path name
//   - pExt point to the dot (.) in the file extension or NULL
// however with this data we instead have the following pointers because rather than
// an absolute path, it is a relative path
//   - pFile points to the last slash in the path name
//   - pExt point to the first dot (.) in the path name as there is no file extension
// the result is that rather than a non-NULL pExt value being larger than pFile,
// it is instead smaller for this specific data.
char *pNameNoExt;
if (pExt) {  // this really should be if (pExt && pFile < pExt) {
    // extension specified so allocate space just for the name, no extension
    // allocate space for just the file name without the extension
    // since pExt is less than pFile, we get a negative value which then becomes
    // a really huge unsigned value.
    pNameNoExt = malloc ((pExt - pFile + 1) * sizeof(char));
} else {
    pNameNoExt = malloc ((strlen(pFile) + 1) * sizeof(char));
}

適切なランタイム メモリ管理では、解放されたメモリのチャンクを合体させようとします。これにより、多くの小さなブロックが、解放されるときに結合されて大きなブロックになります。メモリのチャンクをこのように組み合わせると、C メモリ管理ランタイムによって管理されているメモリのリストまたはヒープで既に使用可能なものを使用して、メモリ要求を処理できない可能性が減少します。

すでに割り当てられているメモリを再利用できるほど、依存度が低くmalloc()なりfree()、より良いものになります。をしていない場合malloc()、失敗するのは難しいです。

多くの小さなサイズの呼び出しをmalloc()より少ない大きな呼び出しに変更できるmalloc()ほど、メモリを断片化し、メモリ リストまたはヒープのサイズを拡大する可能性が低くなります。お互いに。

malloc()連続したブロックをfree()同時に処理できるほど、メモリ管理ランタイムがブロックを結合できる可能性が高くなります。

malloc()オブジェクトの特定のサイズで を実行する必要があるという規則はありません。指定された size 引数はmalloc()、メモリを割り当てるオブジェクトに必要なサイズよりも大きくなる可能性があります。malloc ()そのため、標準サイズのブロックが標準サイズのメモリに切り上げられて割り当てられるように、呼び出しに何らかのルールを使用することができます。したがって、((サイズ / 16) + 1) * 16 または ((サイズ >> 4) + 1) << 4 のような式を使用して、16 バイトのブロックに割り当てることができます。多くのスクリプト言語は、への呼び出しが繰り返される可能性が高くなり、リクエストをリストまたはメモリ ヒープ上の空きブロックmalloc()free()一致させることができます。

これは、割り当てられたブロックと割り当て解除されたブロックの数を減らそうとする、やや単純な例です。可変サイズのメモリ ブロックのリンク リストがあるとします。したがって、リンクされたリスト内のノードの構造体は次のようになります。

typedef struct __MyNodeStruct {
    struct __MyNodeStruct *pNext;
    unsigned char *pMegaBuffer;
} MyNodeStruct;

このメモリを特定のバッファとそのノードに割り当てる方法は 2 つあります。1 つ目は、ノードの標準的な割り当てであり、その後に次のようにバッファの割り当てが続きます。

MyNodeStruct *pNewNode = malloc(sizeof(MyNodeStruct));
if (pNewNode)
    pNewNode->pMegaBuffer = malloc(15000);

ただし、別の方法は、次のようなことを行うことです。これは、ポインター演算で単一のメモリ割り当てを使用して、単一でmalloc()両方のメモリ領域を提供することです。

MyNodeStruct *pNewNode = malloc(sizeof(myNodeStruct) + 15000);
if (pNewNode)
    pNewNode->pMegaBuffer = ((unsigned char *)pNewNode) + sizeof(myNodeStruct);

pMegaBufferただし、この単一の割り当て方法を使用している場合は、誤ってポインターを使用しないように、ポインターの使用に一貫性があることを確認する必要がありますfree()。また、バッファーをより大きなバッファーに変更する必要がある場合は、ノードを解放し、バッファーとノードを再割り当てする必要があります。したがって、プログラマーの仕事は増えます。

于 2012-09-15T05:46:16.470 に答える
-4

新しいメモリを指すポインター自体が割り当てられていないために malloc が失敗するインスタンスを見てきました。

pNewNode = malloc(sizeof(myNodeStruct) + 15000);

何らかの理由で pNewNode を事前に作成または割り当てる必要がある場合、それは無効であり、malloc 割り当ての結果 (それ自体は成功) をポインターに格納できないため、malloc は失敗します。このバグが存在する場合、同じプログラムを複数回実行すると、コードが一部で機能することがわかりました (ポインターが偶然存在する場合、それはまったくの運によるものです)。割り当てられたことはありません。

このバグを見つける方法は?デバッガーで、malloc を呼び出す前に pNewNode が実際に有効かどうかを確認します。0x000000 または他の実際の場所を指している必要があります (malloc が実際に割り当てられたメモリ セグメントを割り当てるまで、これは実際にはガベージです)。

于 2012-11-27T21:52:30.560 に答える