20

我正在将一个 C 项目从 Linux 移植到 Windows。在 Linux 上它是完全稳定的。在 Windows 上,它大部分时间都运行良好,但有时我会遇到分段错误。

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

就像我说的,它不会一直发生,也不会发生在同一个地方。它看起来像一个随机错误。

在 Windows 上我必须比在 Linux 上更小心吗?我做错了什么?

4

4 回答 4

33

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

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

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

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

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

使用现代操作系统和虚拟内存,除非您正在做一些非常大的内存驻留存储,否则内存不足是非常困难的。但是,正如用户 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()对象的特定大小执行 a,提供的 size 参数malloc()可以大于您为其分配内存的对象所需的大小。因此,您可能希望对调用使用某种规则,malloc ()以便通过四舍五入到某个标准内存量来分配标准大小的块。因此,您可以使用 ((size / 16) + 1) * 16 或更可能 ((size >> 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()对其进行操作。如果您必须用更大的缓冲区更改缓冲区,则需要释放节点并重新分配缓冲区和节点。所以程序员有更多的工作。

于 2012-09-15T05:46:16.470 回答
5

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

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

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

于 2016-04-05T00:02:04.647 回答
-4

我见过 malloc 失败的实例,因为指向新内存的指针本身没有分配:

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

如果由于某种原因需要预先创建或分配 pNewNode,它是无效的并且 malloc 将失败,因为 malloc 分配的结果(本身是成功的)不能存储在指针中。当这个错误出现时,我已经看到多次运行同一个程序,代码将在某些情况下工作(当指针意外出现时,但只是运气好),但在许多情况下,它不会指向任何地方,因为它从未分配过。

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

于 2012-11-27T21:52:30.560 回答
-20

您可以基于递归函数声明自己的安全 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));
于 2014-01-02T23:08:16.717 回答