繁体   English   中英

为什么malloc有时不起作用?

[英]Why does malloc not work sometimes?

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

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

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

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

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

因此, malloc()失败的第一种情况是无法满足内存请求,因为(1)C运行时的列表或堆上没有可用的内存块,以及(2)当C运行时内存管理请求时来自操作系统的更多内存,请求被拒绝。

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

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

malloc()可能失败的另一个原因是因为内存管理数据结构已经损坏,可能是由于缓冲区溢出,其中分配的内存区域用于大于分配的内存大小的对象。 不同版本的malloc()可以使用不同的策略进行内存管理,并确定调用malloc()时要提供多少内存。 例如, malloc()可以准确地为您提供所请求的字节数,或者它可能比您要求的更多,以便适合在内存边界内分配的块或使内存管理更容易。

使用现代操作系统和虚拟内存,除非您正在执行一些非常大的内存驻留,否则很难耗尽内存。 但是正如用户Yeow_Meng在下面的评论中提到的,如果你正在做算术来确定要分配的大小而结果是负数,你可能最终会请求大量的内存,因为malloc()的参数为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()的size参数可以大于为其分配内存的对象所需的大小。 因此,您可能希望使用某种规则来调用malloc ()以便通过舍入到某些标准内存量来分配标准大小的块。 因此,您可以使用类似((size / 16)+ 1)* 16或更多可能的公式((size >> 4)+ 1)来分配16字节的块。<< 4.许多脚本语言使用类似的东西以便增加重复调用malloc()free()的机会,使得能够将请求与列表或内存堆上的空闲块进行匹配。

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

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

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

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() 如果您不得不使用更大的缓冲区更改缓冲区,则需要释放节点并重新分配缓冲区和节点。 所以程序员还有更多的工作要做。

malloc()在Windows上失败的另一个原因是你的代码在一个DLL中分配并在另一个DLL或EXE中释放。

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

不同的运行时可能会以不同的方式处理堆。 调试和发布CRT 肯定会以不同方式处理堆。 如果你在Debug中的malloc()和Release中的free() ,它会破坏,这可能会导致你的问题。

我已经看到malloc失败的实例,因为指向新内存的指针本身本身没有分配:

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

如果由于某种原因需要先前创建或分配pNewNode,则它无效并且malloc将失败,因为malloc分配的结果(其本身是成功的)不能存储在指针中。 当这个bug出现时,我已经看到多次运行相同的程序,代码将在某些程序中运行(当指针意外出现时,但仅仅是纯粹的运气),但在许多情况下它将无处可寻它从未被分配过。

如何找到这个bug? 在调试器中查看pNewNode在调用malloc之前是否实际有效。 它应该指向0x000000或其他一些实际位置(在malloc分配实际分配的内存段之前实际上是垃圾)。

您可以基于递归函数声明自己的安全malloc:

void *malloc_safe(size_t size)
{
    void* ptr = malloc(size);
    if(ptr == NULL)
        return malloc_safe(size); 
    else
        return ptr;
}

如果malloc失败,则此函数再次调用并尝试分配内存,而ptr变为!= NULL。

使用:

int *some_ptr = (int *)malloc_safe(sizeof(int));

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM