为什么malloc有时不起作用?

Ped*_*ves 16 c windows malloc porting segmentation-fault

我正在将一个C项目从Linux移植到Windows.在Linux上它完全稳定.在Windows上,它大多数时候运行良好,但有时我遇到了分段错误.

我正在使用Microsoft Visual Studio 2010进行编译和调试,看起来有时我的malloc调用只是不分配内存,返回NULL.机器有空闲内存; 它已经通过该代码一千次,但它仍然发生在不同的位置.

就像我说的那样,它并不是一直发生在同一个地方; 它看起来像一个随机错误.

我在Windows上需要比在Linux上更小心吗?我能做错什么?

Ric*_*ers 27

malloc()当无法为内存请求提供服务时,返回NULL的无效指针.在大多数情况下,C内存分配例程管理内存可用内存的列表或堆,调用操作系统以在进行malloc()调用时分配额外的内存块,并且列表或堆上没有块来满足请求.

因此,第一种malloc()失败的情况是,由于列表或堆上没有可用的内存块,并且当C运行时内存管理从操作系统请求更多内存时,无法满足内存请求,因此请求被拒绝.

这是一篇关于指针分配策略的文章.

这篇论坛文章给出了由于内存碎片导致malloc失败的示例.

malloc()可能失败的另一个原因是因为内存管理数据结构可能由于缓冲区溢出而损坏.不同版本的malloc()可以使用不同的内存管理策略,并确定malloc()调用时提供的内存量.例如,a 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));
}
Run Code Online (Sandbox Code Playgroud)

良好的运行时内存管理将尝试合并释放的内存块,以便在释放时将许多较小的块组合成更大的块.这种内存块的组合减少了无法使用C内存管理运行时管理的列表或内存堆上已有的内容来服务内存请求的可能性.

那你可以重新使用已分配的内存越来越少,你依靠malloc()free()更好的.如果你不做,malloc()那么它很难失败.

您可以将更多小型调用更改malloc()为更少的大型调用,从而malloc()将碎片内存和扩展内存列表或堆的大小的可能性降低到更多,这些小块无法组合,因为它们不是下一个对彼此.

您可以同时malloc()free()连续的块越多,内存管理运行时间就越有可能合并块.

没有规则说您必须malloc()使用特定大小.因此,您可能希望使用某种规则进行调用,malloc()以便分配标准大小的块,以便使用类似((size/16)+ 1)*16或更多可能的公式(以下情况)分配16字节的块(( size >> 4)+ 1)<< 4.许多脚本语言使用类似的东西,以增加重复调用malloc ()malloc()能够将请求与列表或内存堆上的空闲块匹配的机会.

这是一个尝试减少分配和解除分配的块数的简单示例.让我们说我们有一个可变大小的内存块的链表.因此,链表中节点的结构如下所示:

typedef struct __MyNodeStruct {
    struct __MyNodeStruct *pNext;
    unsigned char *pMegaBuffer;
} MyNodeStruct;
Run Code Online (Sandbox Code Playgroud)

可以有两种方法为特定缓冲区及其节点分配此内存.第一个是节点的标准分配,然后是缓冲区的分配,如下所示.

MyNodeStruct *pNewNode = malloc(sizeof(MyNodeStruct));
if (pNewNode)
    pNewNode->pMegaBuffer = malloc(15000);
Run Code Online (Sandbox Code Playgroud)

然而,另一种方法是执行类似下面的操作,使用带有指针运算的单个内存分配,以便单个free()提供两个内存区域.

MyNodeStruct *pNewNode = malloc(sizeof(myNodeStruct) + 15000);
if (pNewNode)
    pNewNode->pMegaBuffer = ((unsigned char *)pNewNode) + sizeof(myNodeStruct);
Run Code Online (Sandbox Code Playgroud)

但是,如果您使用此单一分配方法,则需要确保使用指针时保持一致,而malloc()不会意外pMegaBuffer地对其进行操作.如果您不得不使用更大的缓冲区更改缓冲区,则需要释放节点并重新分配缓冲区和节点.所以程序员还有更多的工作要做.

  • `malloc(-1)` 或其他负数可能会失败,因为 `malloc` 采用类型为 `size_t` 的无符号数据类型。因此,您请求的大小被隐式转换为正数据类型。隐式转换可能会产生非常大的正数,并且您对 `malloc` 的调用失败,因为操作系统不能(或不会)给您那么多内存。 (2认同)

Zan*_*ynx 5

malloc()在 Windows 上失败的另一个原因是,如果您的代码在一个 DLL 中分配并在不同的 DLL 或 EXE 中释放。

与 Linux 不同,在 Windows 中,DLL 或 EXE 有自己的指向运行时库的链接。这意味着您可以使用 2013 CRT 将您的程序链接到针对 2008 CRT 编译的 DLL。

不同的运行时可能会以不同的方式处理堆。Debug 和 Release CRT肯定会以不同的方式处理堆。如果您malloc()在 Debug 和free()Release 中,它会严重崩溃,这可能会导致您的问题。