web-dev-qa-db-ja.com

mallocが機能しないことがあるのはなぜですか?

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

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

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

LinuxよりもWindowsで注意しなければならないことはありますか?何が悪いのでしょうか?

17
Pedro Alves

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

したがって、malloc()が失敗する最初のケースは、(1)Cランタイムのリストまたはヒープに使用可能なメモリブロックがないため、(2)Cランタイムメモリ管理がオペレーティングシステムからより多くのメモリを要求したため、要求は拒否されました。

ポインタ割り当て戦略 に関する記事です。

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

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

最新のオペレーティングシステムと仮想メモリでは、非常に大容量のメモリ常駐ストレージを実行しない限り、メモリ不足になることはかなり困難です。ただし、ユーザー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()を実行する必要があるというルールはありません。malloc()に提供されるサイズ引数は、目的のオブジェクトに必要なサイズよりも大きくなる可能性がありますメモリを割り当てています。そのため、malloc ()の呼び出しにある種のルールを使用して、標準サイズのブロックが標準メモリ量に切り上げられるように割り当てることができます。したがって、((size/16)+ 1)* 16以上の((size >> 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);
_

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

28

Windowsでmalloc()が失敗するもう1つの理由は、コードがあるDLLに割り当て、別のDLLまたはEXEで割り当てを解除する場合です。

Linuxとは異なり、WindowsではDLLまたはEXEにランタイムライブラリへの独自のリンクがあります。つまり、2013 CRTを使用して、2008 CRTに対してコンパイルされたDLLにプログラムをリンクできます。

ランタイムが異なると、ヒープの処理が異なる場合があります。デバッグとリリースのCRT definitelyは、ヒープを異なる方法で処理します。デバッグでmalloc()を、リリースでfree()を使用すると、ひどく壊れ、問題が発生する可能性があります。

4
Zan Lynx