简体   繁体   English

为什么malloc有时不起作用?

[英]Why does malloc not work sometimes?

I'm porting a C project from Linux to Windows. 我正在将一个C项目从Linux移植到Windows。 On Linux it is completely stable. 在Linux上它完全稳定。 On Windows, it's working well most times, but sometimes I got a segmentation fault. 在Windows上,它大多数时候运行良好,但有时我遇到了分段错误。

I'm using Microsoft Visual Studio 2010 to compile and debug and looks like sometimes my malloc calls simply doesn't allocate memory, returning NULL. 我正在使用Microsoft Visual Studio 2010进行编译和调试,看起来有时我的malloc调用只是不分配内存,返回NULL。 The machine has free memory; 机器有空闲内存; it already passed through that code a thousand times, but it still happens in different locations. 它已经通过该代码一千次,但它仍然发生在不同的位置。

Like I said, it doesn't happen all the time or in the same location; 就像我说的那样,它并不是一直发生在同一个地方; it looks like a random error. 它看起来像一个随机错误。

Is there something I have to be more careful on Windows than on Linux? 我在Windows上需要比在Linux上更小心吗? What can I be doing wrong? 我能做错什么?

malloc() returns an invalid pointer of NULL when it is unable to service a memory request. malloc()在无法为内存请求提供服务时返回NULL的无效指针。 In most cases the C memory allocation routines manage a list or heap of memory available memory with calls to the operating system to allocate additional chunks of memory when a malloc() call is made and there is not a block on the list or heap to satisfy the request. 在大多数情况下,C内存分配例程管理内存可用内存的列表或堆,调用操作系统以在进行malloc()调用时分配额外的内存块,并且列表或堆上没有块来满足请求。

So the first case of malloc() failing is when a memory request can not be satisfied because (1) there is not a usable block of memory on the list or heap of the C runtime and (2) when the C runtime memory management requested more memory from the operating system, the request was refused. 因此, malloc()失败的第一种情况是无法满足内存请求,因为(1)C运行时的列表或堆上没有可用的内存块,以及(2)当C运行时内存管理请求时来自操作系统的更多内存,请求被拒绝。

Here is an article about Pointer Allocation Strategies . 这是一篇关于指针分配策略的文章。

This forum article gives an example of malloc failure due to memory fragmentation . 这篇论坛文章给出了由于内存碎片导致malloc失败的示例。

Another reason why malloc() might fail is because the memory management data structures have become corrupted probably due to a buffer overflow in which a memory area that was allocated was used for an object larger than the size of the memory allocated. malloc()可能失败的另一个原因是因为内存管理数据结构已经损坏,可能是由于缓冲区溢出,其中分配的内存区域用于大于分配的内存大小的对象。 Different versions of malloc() can use different strategies for memory management and determining how much memory to provide when malloc() is called. 不同版本的malloc()可以使用不同的策略进行内存管理,并确定调用malloc()时要提供多少内存。 For instance a malloc() may give you exactly the number of bytes requested or it may give you more than you asked for in order to fit the block allocated within memory boundaries or to make the memory management easier. 例如, malloc()可以准确地为您提供所请求的字节数,或者它可能比您要求的更多,以便适合在内存边界内分配的块或使内存管理更容易。

With modern operating systems and virtual memory, it is pretty difficult to run out of memory unless you are doing some really large memory resident storage. 使用现代操作系统和虚拟内存,除非您正在执行一些非常大的内存驻留,否则很难耗尽内存。 However as user Yeow_Meng mentioned in a comment below, if you are doing arithmetic to determine the size to allocate and the result is a negative number you could end up requesting a huge amount of memory because the argument to malloc() for the amount of memory to allocation is unsigned. 但是正如用户Yeow_Meng在下面的评论中提到的,如果你正在做算术来确定要分配的大小而结果是负数,你可能最终会请求大量的内存,因为malloc()的参数为malloc()量分配是无符号的。

You can run into the problem of negative sizes when doing pointer arithmetic to determine how much space is needed for some data. 在进行指针运算时,您可能会遇到负大小的问题,以确定某些数据需要多少空间。 This kind of error is common for text parsing that is done on text that is unexpected. 对于意外的文本进行的文本解析,这种错误很常见。 For example the following code would result in a very large malloc() request. 例如,以下代码将导致非常大的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));
}

A good run time memory management will try to coalesce freed chunks of memory so that many smaller blocks will be combined into larger blocks as they are freed. 良好的运行时内存管理将尝试合并释放的内存块,以便在释放时将许多较小的块组合成更大的块。 This combining of chunks of memory reduces the chances of being unable to service a memory request using what is already available on the list or heap of memory being managed by the C memory management run time. 这种内存块的组合减少了无法使用C内存管理运行时管理的列表或内存堆上已有的内容来服务内存请求的可能性。

The more that you can just reuse already allocated memory and the less you depend on malloc() and free() the better. 你可以重用已经分配的内存越多,你依赖malloc()free()越少越好。 If you are not doing a malloc() then it is difficult for it to fail. 如果你没有做malloc()那么它就很难失败。

The more that you can change many small size calls to malloc() to fewer large calls to malloc() the less chance you have for fragmenting the memory and expanding the size of the memory list or heap with lots of small blocks that can not be combined because they are not next to each other. 您可以将对malloc()的许多小型调用更改为更少的对malloc()的大型调用,将碎片化内存和扩展内存列表或堆的大小的可能性降低到更多,这些小块不可能是因为它们不是彼此相邻而结合在一起。

The more that you can malloc() and free() contiguous blocks at the same time, the more likely that the memory management run time can coalesce blocks. 您可以同时使用malloc()free()连续块越多,内存管理运行时间就越有可能合并块。

There is no rule that says you must do a malloc() with the specific size of an object, the size argument provided to malloc() can be larger than the size needed for the object for which you are allocating memory. 没有规则说你必须使用对象的特定大小来执行malloc() ,提供给malloc()的size参数可以大于为其分配内存的对象所需的大小。 So you may want to use some kind of a rule for calls to malloc () so that standard sized blocks are allocated by rounding up to some standard amount of memory. 因此,您可能希望使用某种规则来调用malloc ()以便通过舍入到某些标准内存量来分配标准大小的块。 So you may allocate in blocks of 16 bytes using a formula like ((size / 16) + 1) * 16 or more likely ((size >> 4) + 1) << 4. Many script languages use something similar so as to increase the chance of repeated calls to malloc() and free() being able to match up a request with a free block on the list or heap of memory. 因此,您可以使用类似((size / 16)+ 1)* 16或更多可能的公式((size >> 4)+ 1)来分配16字节的块。<< 4.许多脚本语言使用类似的东西以便增加重复调用malloc()free()的机会,使得能够将请求与列表或内存堆上的空闲块进行匹配。

Here is a somewhat simple example of trying to reduce the number of blocks allocated and deallocated. 这是一个尝试减少分配和解除分配的块数的简单示例。 Lets say that we have a linked list of variable sized blocks of memory. 让我们说我们有一个可变大小的内存块的链表。 So the struct for the nodes in the linked list look something like: 因此,链表中节点的结构如下所示:

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

There could be two ways of allocating this memory for a particular buffer and its node. 可以有两种方法为特定缓冲区及其节点分配此内存。 The first is a standard allocation of the node followed by an allocation of the buffer as in the following. 第一个是节点的标准分配,然后是缓冲区的分配,如下所示。

MyNodeStruct *pNewNode = malloc(sizeof(MyNodeStruct));
if (pNewNode)
    pNewNode->pMegaBuffer = malloc(15000);

However another way would be to do something like the following which uses a single memory allocation with pointer arithmetic so that a single malloc() provides both memory areas. 然而,另一种方法是执行类似下面的操作,使用带有指针运算的单个内存分配,以便单个malloc()提供两个内存区域。

MyNodeStruct *pNewNode = malloc(sizeof(myNodeStruct) + 15000);
if (pNewNode)
    pNewNode->pMegaBuffer = ((unsigned char *)pNewNode) + sizeof(myNodeStruct);

However if you are using this single allocation method, you will need to make sure that you are consistent in the use of the pointer pMegaBuffer that you do not accidently do a free() on it. 但是,如果您使用这种单一分配方法,则需要确保使用指针pMegaBuffer是一致的,您不会意外地对其执行free() And if you are having to change out the buffer with a larger buffer, you will need to free the node and reallocate buffer and node. 如果您不得不使用更大的缓冲区更改缓冲区,则需要释放节点并重新分配缓冲区和节点。 So there is more work for the programmer. 所以程序员还有更多的工作要做。

Another reason for malloc() to fail on Windows is if your code allocates in one DLL and deallocates in a different DLL or EXE. malloc()在Windows上失败的另一个原因是你的代码在一个DLL中分配并在另一个DLL或EXE中释放。

Unlike Linux, in Windows a DLL or EXE has its own links to the runtime libraries. 与Linux不同,在Windows中,DLL或EXE具有自己的运行时库链接。 That means that you can link your program, using the 2013 CRT to a DLL compiled against the 2008 CRT. 这意味着您可以使用2013 CRT链接您的程序到针对2008 CRT编译的DLL。

The different runtimes might handle the heap differently. 不同的运行时可能会以不同的方式处理堆。 The Debug and Release CRTs definitely handle the heap differently. 调试和发布CRT 肯定会以不同方式处理堆。 If you malloc() in Debug and free() in Release, it will break horribly, and this might be causing your problem. 如果你在Debug中的malloc()和Release中的free() ,它会破坏,这可能会导致你的问题。

I've seen instances where malloc fails because the pointer itself which will point to the new memory is itself not allocated: 我已经看到malloc失败的实例,因为指向新内存的指针本身本身没有分配:

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

If for some reason pNewNode needed to be previously created or allocated, it's invalid and malloc will fail since the result of the malloc allocation (which is itself successfull) can't be stored in the pointer. 如果由于某种原因需要先前创建或分配pNewNode,则它无效并且malloc将失败,因为malloc分配的结果(其本身是成功的)不能存储在指针中。 When this bug is present, I've seen that running the same program multiple times, the code will work in some (when the pointer is accidentally present, but just by sheer luck), but in many of the cases it will point nowhere since it was never allocated. 当这个bug出现时,我已经看到多次运行相同的程序,代码将在某些程序中运行(当指针意外出现时,但仅仅是纯粹的运气),但在许多情况下它将无处可寻它从未被分配过。

How to find this bug? 如何找到这个bug? In your debugger look at whether pNewNode is actually valid before the call to malloc. 在调试器中查看pNewNode在调用malloc之前是否实际有效。 it should be pointing to 0x000000 or some other real location (which is actually garbage until the malloc assigns an actual allocated memory segment). 它应该指向0x000000或其他一些实际位置(在malloc分配实际分配的内存段之前实际上是垃圾)。

You can declare your own safe malloc based on recursive function: 您可以基于递归函数声明自己的安全malloc:

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

If malloc fails, this function is calling again and trying to allocate memory while ptr becomes != NULL. 如果malloc失败,则此函数再次调用并尝试分配内存,而ptr变为!= NULL。

using: 使用:

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

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

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