我正在将一个C项目从Linux移植到Windows。 在Linux上它是完全稳定的。 在Windows上,大多数时候都运行良好,但有时会出现分段错误。
我正在使用Microsoft Visual Studio 2010进行编译和debugging,看起来像有时我的malloc调用根本不分配内存,返回NULL。 机器有空闲的内存; 它已经通过了这个代码一千次,但它仍然发生在不同的位置。
就像我说的那样,它不会一直发生在同一个地方, 它看起来像一个随机错误。
有什么我必须在Windows上比在Linux上更小心吗? 我能做什么错了?
malloc()
在无法处理内存请求时返回NULL的无效指针。 在大多数情况下,C内存分配例程管理一个或一堆内存可用内存,调用操作系统以在调用malloc()
时分配额外的内存块,并且列表或堆中没有块来满足请求。
所以malloc()
失败的第一种情况是当内存请求不能被满足,因为在列表或堆中没有可用的内存块,并且当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 / 16)+ 1)* 16或更大的公式分配16个字节的块很可能((size >> 4)+ 1)<< 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出现的时候,我已经看到多次运行相同的程序,代码将会在某些时候(当指针意外地出现,但仅仅是运气好时)起作用,但是在许多情况下,它从未被分配。
如何找到这个错误? 在你的调试器中,看看在调用malloc之前pNewNode是否真的有效。 它应该指向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));