我正在将一个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()
请求。
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 ()
调用使用某种规则,以便通过向上舍入到某个标准内存量来分配标准大小的块。因此,您可以使用 ((大小/16) + 1) * 16 或更有可能((大小>> 4) + 1) <<4 这样的公式以 16 字节的块进行分配。 许多脚本语言使用类似的东西,以增加重复调用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 处理堆的方式肯定不同。如果您在"调试"中malloc()
并在"发布"中free()
,它将严重中断,这可能会导致您的问题。
我见过malloc失败的情况,因为指向新内存的指针本身没有分配:
pNewNode = malloc(sizeof(myNodeStruct) + 15000);
如果由于某种原因需要事先创建或分配 pNewNode,则它是无效的,malloc 将失败,因为 malloc 分配的结果(本身是成功的)无法存储在指针中。当出现此错误时,我已经看到多次运行同一程序,代码将在某些代码中工作(当指针意外出现时,但纯粹是运气),但在许多情况下,它不会指向任何地方,因为它从未被分配过。
如何找到此错误?在调试器中,查看 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));