【问题标题】:What if malloc fails?如果 malloc 失败了怎么办?
【发布时间】:2025-12-27 04:30:11
【问题描述】:

如果malloc 分配失败,我们应该再试一次吗?

在这样的情况下:

char* mystrdup(const char *s)  
{
    char *ab = NULL;

    while(ab == NULL) {
        ab=(char*)malloc(strlen(s)+1);  
    }

    strcpy(ab, s);
    return ab;
}

while 循环对于检查内存分配是否有效?

【问题讨论】:

    标签: c memory malloc


    【解决方案1】:

    一般来说,现代的malloc() 实现将返回NULL 仅作为绝对的最后手段,再试一次肯定无济于事。唯一有用的是释放一些内存,然后再试一次。如果您的应用程序拥有任何消耗性资源,那么现在是释放它们的时候了,然后再试一次。

    在某些环境中,一种有用的做法是分配少量内存作为未雨绸缪。如果malloc() 曾经确实返回NULL,您可以释放该未雨绸缪的资金,然后分配您需要的任何资源,以便能够处理错误并优雅地退出。这是使用旧的 Macintosh Toolbox 编程时的常见做法。如果malloc() 返回NULL,您可以使用该空间创建一个对话框以在退出前报告问题。

    【讨论】:

      【解决方案2】:

      在单线程程序中“重试”而不释放任何内存在尝试之间没有实际意义。它会永远循环下去。

      在多线程程序中,如果另一个并行运行的线程突然决定释放一些自己的内存,这可能会“起作用”。这种情况下的循环将构成一个经典的“忙等待”循环。但即使在这种情况下,这样的代码也几乎没有什么实用价值,原因不止一个。

      【讨论】:

      • 如果失败的原因是系统上的总内存耗尽,而不仅仅是每个进程的限制,例如虚拟地址空间的大小或setrlimit 限制。
      【解决方案3】:

      不,从来没有。如果malloc 返回 NULL,则表示错误,您应该中止。

      【讨论】:

        【解决方案4】:

        无需争论为什么或何时有用,尝试在循环中重新分配可能会起作用,至少在具有 64 位代码和默认页面文件设置的 Windows 上。此外,这可能会购买更多额外的虚拟内存。虽然,不要在无限循环中执行此操作,而是使用有限次数的重试。作为证明,请尝试以下模拟泄漏 1 Mb 内存的代码。您必须在 Release 版本中运行它,最好不要在调试器下运行。

        for (int i = 0; i < 10; i++)
        {
          size_t allocated = 0;
          while (1)
          {
            void* p = malloc(1024 * 1024);
            if (!p)
              break;
        
            allocated += 1;
          }
        
          //This prints only after malloc had failed.
          std::cout << "Allocated: " << allocated << " Mb\n";
          //Sleep(1000);
        }
        

        在具有 8 Gb RAM 和系统管理页面文件的机器上,我得到以下输出(针对 x64 目标使用 VS2013 构建,在 Windows 7 Pro 上测试):

        Allocated: 14075 Mb
        Allocated: 16 Mb
        Allocated: 2392 Mb
        Allocated: 3 Mb
        Allocated: 2791 Mb
        Allocated: 16 Mb
        Allocated: 3172 Mb
        Allocated: 16 Mb
        Allocated: 3651 Mb
        Allocated: 15 Mb
        

        我不知道这种行为的确切原因,但似乎一旦页面文件调整大小无法跟上请求,分配就会开始失败。 在我的机器上,页面文件在此循环后从 8 GB 增长到 20 Gb(在程序退出后降回 8 Gb)。

        【讨论】:

        【解决方案5】:

        这不太可能满足您的要求;如果您的内存不足,那么忙于循环直到您获得更多可能会令人失望。您应该将 NULL 返回给调用程序,以便它可以通过释放不再需要的内存或返回错误来处理资源耗尽。

        【讨论】:

          【解决方案6】:

          malloc() 尽力分配内存。如果失败,不要在while循环中重新尝试分配内存(程序可能会永远卡在那里),如果可以的话,尝试释放一些其他进程或线程持有的内存,然后重试。

          另一种选择是增加内存,通过增加交换文件或分页内存,在运行中,从代码本身(但它危险且不优选)或手动执行。

          避免此类问题的最佳方法是在编写代码时计算或估计内存需求。

          【讨论】:

            【解决方案7】:

            尝试增加堆大小(为动态分配预留的内存)。

            【讨论】:

              【解决方案8】:

              这取决于您的软件是做什么用的以及您的代码的哪一部分受到影响。

              首先要知道,当没有可用页面时 malloc() 可能会失败,如果您的应用程序达到它的限制,那么任何循环都不会工作,但是如果您的系统内存不足,值得一试,但您必须避免任何无限循环.惊喜!如果操作系统临时响应无法分配更多RAM,这完全正常,您可以按自己的方式处理。

              这是一个很好的问题

              类似的问题是没有捕获信号,当多线程或异步 TCP 服务器的客户端连接中断时,软件会被 SIGPIPE 终止。这是正常的,但会导致您的程序结束,但它不应该。为了防止这种情况,你必须挂钩信号。

              在现实世界的例子中(我自己的)。

              当 malloc 失败并且只影响我的部分代码时

              我曾经在新连接发送数据时使用 malloc() 或 new[],我将接收到的数据存储到缓冲区中,如果 malloc 或 realloc 失败,函数返回 false 并释放缓冲区,连接因错误而断开(内衣)所以在这种情况下,软件会继续运行,但一个连接断开了。我认为这是正确的方法。

              当 malloc 必须导致软件中止时

              我曾经使用 malloc() 为关键数据腾出空间,例如数组、定义核心的结构,这通常在软件开始时作为 init 部分运行,如果 malloc() 失败,软件必须中止并以错误代码退出,因为所有操作都取决于必须填充数据的表。 (内置文件系统)

              当 malloc 能够重试时

              我的数据记录器软件属于行业领先的类型(高可用性),如果 malloc() 失败,我会触发 mutex_lock(),这会导致后端软件冻结并尝试重试 malloc 过程 X 秒。如果 malloc() 继续失败,软件开始在所有线程上调用析构函数并执行完全关闭,但此时尝试 malloc() 不成功的线程有两个选项,malloc() 成功并完成堆栈调用和退出最后一个线程或回滚并退出最后一个线程。

              每当发生这种情况时,软件也不会退出,请尝试从头开始等等..

              也许值得一提..

              几年前我也遇到过同样的困境,我的软件中出现了内存泄漏并占用了我所有的 RAM,但为了保存当前状态,我不得不在多次 malloc() 之后将其写出来,解决方案是这发生了,我关闭了所有线程并调用析构函数并保存了数据,但有趣的是当我关闭所有连接并释放套接字时,ssl_ctx,...内存消耗下降到 128KB,经过几天的愉快调试,我想通了SSL_CTX 有内部存储和缓存。所以现在当没有在线连接时,我释放 SSL_CTX 并像魅力一样工作。

              总结

              所以你可以看到这是一门艺术,你可以用 malloc() 做你想做的事,任何事情都取决于你,没有书也没有标准,如果 malloc() 失败了你应该怎么做。如果有人告诉你应该怎么做,那只是他的意见而已。

              我避免无限循环的首选方法

              PSEUDO CODE:
              
              var ts = TIME()
              var max_seconds = 5
              var success = true
              
              WHILE (MALLOC() == FAIL) DO
                 IF TS + max_seconds < TIME() THEN
                    success = false
                    BREAK
                 END
                 SLEEP(100ms)
              END
              
              
              

              【讨论】:

                【解决方案9】:

                根据我的经验 (UNIX),在网络管理员吹嘘“您想要的新服务器已经存在并准备就绪”之后,立即稳健地处理 malloc 故障是最重要的。一个典型的回复是这样的:“哇,太快了,谢谢”。它在此时 malloc 将返回一个 NULL。

                最好的办法是抛出一个带有静态错误消息的 std::bad_alloc 异常,该消息被捕获并显示在 main() 中。希望您的析构函数进行足够的清理以释放内存,以免 main 中的错误处理失败。

                如果您使用合理的操作系统和编程风格,OOM 情况是可以恢复的。

                【讨论】:

                • "最好的办法是抛出一个 std::bad_alloc" --> C 中没有 std::bad_alloc 异常。
                【解决方案10】:

                您没有正确分配内存 malloc 函数需要参数作为它需要分配的字节数,但是您正在传递字符串的长度,因此 malloc 将根据长度而不是字符串使用的字节保留内存。您应该改用 sizeof() ,如果仍然失败,则 malloc 返回 null ,表示堆栈中没有足够的可用内存来满足您的要求。 要正确运行您的代码,请尝试以下操作:-

                char* mystrdup(const char *s)
                { 字符 *ab = NULL;

                while(ab == NULL) {
                    ab=(char*)malloc(sizeof(s));  
                }
                
                strcpy(ab, s);
                return ab;
                

                }

                【讨论】:

                • 恐怕你弄错了:s是指向char的指针,sizeof(s)是指针的大小,而不是存储指向的字符串所需的字节数sstrlen(s) + 1 是绝对正确的,因为根据定义,char 的大小为 1 字节,强制转换为 (char *) 是可选的,但如果通过包含 &lt;stdlib.h&gt; 正确声明了 malloc(),则无害。