从NULL恢复策略== malloc()由于内存耗尽

阅读Martin Sustrick关于在C ++和C中防止“未定义行为”的挑战的博客,特别是由于内存耗尽而导致malloc()失败的问题时,我想起了很多很多次,我很沮丧地知道在这种情况下做。

对于虚拟系统而言,这种情况很少见,但在嵌入式平台上,或者与虚拟系统相关的性能下降等同于失败的情况下,正如Martin对ZeroMQ的情况一样,我决心找到一个可行的解决方案,并且做到了。

我想询问StackOverflow的读者是否尝试了这种方法,以及他们的经验。

解决方案是在程序开始时通过调用malloc()从堆中分配一块备用内存,然后使用该备用内存池避免内存耗尽。 这个想法是为了防止投降而支持有序的撤退(我昨天晚上正在阅读凯塞林对意大利的防御账户),错误消息和IP套接字等将工作足够长的时间(希望)至少告诉用户发生了什么事情。

#define SPARE_MEM_SIZE (1<<20)  // reserve a megabyte
static void *gSpareMem;

// ------------------------------------------------------------------------------------------------
void *tenacious_malloc(int requested_allocation_size)   {
    static int remaining_spare_size = 0;    // SPARE_MEM_SIZE;
    char err_msg[512];
    void *rtn = NULL;

    // attempt to re-establish the full size of spare memory, if it needs it
    if (SPARE_MEM_SIZE != remaining_spare_size) {
        if(NULL != (gSpareMem = realloc(gSpareMem, SPARE_MEM_SIZE))) {
            remaining_spare_size = SPARE_MEM_SIZE;
            // "touch" the memory so O/S will allocate physical memory
            meset(gSpareMem, 0, SPARE_MEM_SIZE);
            printf("nSize of spare memory pool restored successfully in %s:%s at line %i :)n",
                            __FILE__, __FUNCTION__, __LINE__);
        }   else   {
            printf("nUnable to restore size of spare memory buffer.n");
        }
    }
    // attempt a plain, old vanilla malloc() and test for failure
    if(NULL != (rtn = malloc(requested_allocation_size))) {
        return rtn;
    }   else  {
        sprintf(err_msg, "nInitial call to malloc() failed in %s:%s at line %i",
                                                __FILE__, __FUNCTION__, __LINE__);
        if(remaining_spare_size < requested_allocation_size)    {
            // not enough spare storage to satisfy the request, so no point in trying
            printf("%snRequested allocaton larger than remaining pool. :(nt --- ABORTING --- n", err_msg);
            return NULL;
        }   else   {
            // take the needed storage from spare memory
            printf("%snRetrying memory allocation....n", err_msg);
            remaining_spare_size -= requested_allocation_size;
            if(NULL != (gSpareMem = realloc(gSpareMem, remaining_spare_size))) {
                // return malloc(requested_allocation_size);
                if(NULL != (rtn = malloc(requested_allocation_size))) {
                    printf("Allocation from spare pool succeeded in %s:%s at line %i :)n",
                                            __FILE__, __FUNCTION__, __LINE__);
                    return rtn;
                }   else  {
                    remaining_spare_size += requested_allocation_size;
                    sprintf(err_msg, "nRetry of malloc() after realloc() of spare memory pool "
                        "failed in %s:%s at line %i  :(n", __FILE__, __FUNCTION__, __LINE__);
                    return NULL;
                }
            }   else   {
                printf("nRetry failed.nUnable to allocate requested memory from spare pool. :(n");
                return NULL;
            }
        }
    }   
}
// ------------------------------------------------------------------------------------------------
int _tmain(int argc, _TCHAR* argv[])    {
    int     *IntVec = NULL;
    double  *DblVec = NULL;
    char    *pString = NULL;
    char    String[] = "Every good boy does fine!";

    IntVec = (int *) tenacious_malloc(100 * sizeof(int));
    DblVec = (double *) tenacious_malloc(100 * sizeof(double));
    pString = (char *)tenacious_malloc(100 * sizeof(String));

    strcpy(pString, String);
    printf("n%s", pString);


    printf("nHit Enter to end program.");
    getchar();
    return 0;
}

最好的策略是瞄准没有分配的代码。 特别是,对于一个正确,健壮的程序, 所有失败路径必须是无故障的,这意味着您不能在失败路径中使用分配。

我希望尽可能在操作开始后避免任何分配,而不是在操作开始之前确定所需的存储空间并将其分配。 这可以大大简化程序逻辑并使测试变得更容易(因为您必须测试一个可能的失败点)。 当然,在其他方面它也可能更昂贵; 例如,您可能需要对输入数据进行两次传递才能确定需要多少存储空间,然后使用存储进行处理。

关于你的解决方案,一旦malloc失败,预先分配一些紧急存储来使用,基本上有两个版本:

  • 只需在紧急存储器上free拨打电话,然后希望malloc再次工作。
  • 遍历你自己的包装层,其中包装层可以直接使用紧急存储,而无需释放它。
  • 第一种方法的优点是,即使是标准库和第三方库代码也可以利用紧急空间,但其缺点是释放的存储空间可能会被其他进程或自己进程中的线程抢走。 如果您确定内存耗尽将来自耗尽虚拟地址空间(或处理资源限制)而不是系统资源,并且您的进程是单线程的,那么您不必担心竞争,并且可以相当安全假设这种方法可行。 但是,总体而言,第二种方法更安全,因为您有绝对的保证,可以获得所需数量的紧急存储。

    我不太喜欢这两种方法,但他们可能是你能做的最好的。


    在现代的64位计算机上,你可以使用比RAM更多的内存。 实际上,malloc不会失败。 在实践中发生的事情是,你的应用程序开始颠簸,一旦你有4GB的内存,并且你的分配超过了这个分配,你的性能就会下降到零,因为你正在交换生气。 你的性能下降太多,你永远无法到达malloc无法返回内存的地步。

    链接地址: http://www.djcxy.com/p/82719.html

    上一篇: Strategy for recovering from NULL == malloc() due to memory exhaustion

    下一篇: Why memset function make the virtual memory so large