如果malloc
分配失败,我们应该再试一次吗?
在这样的事情中:
char* mystrdup(const char *s)
{
char *ab = NULL;
while(ab == NULL) {
ab=(char*)malloc(strlen(s)+1);
}
strcpy(ab, s);
return ab;
}
while 循环对于检查内存分配是否有效?
一般来说,现代malloc()
实现NULL
只会作为绝对的最后手段返回,再试一次肯定无济于事。唯一有帮助的是释放一些内存,然后再试一次。如果您的应用程序拥有任何消耗性资源,这将是释放它们的时候,然后再试一次。
在某些环境中,一个有用的做法是分配少量内存作为未雨绸缪的资金。如果确实return ,您可以释放该未雨绸缪malloc()
的资金,然后分配您需要的任何资源,以便能够处理错误并优雅地退出。这是使用旧的 Macintosh Toolbox 编程时的常见做法。如果返回,您可以使用该空间创建一个对话框以在退出之前报告问题。NULL
malloc()
NULL
在单线程程序中“重试”而不在尝试之间释放任何内存没有实际意义。它只会永远循环。
在多线程程序中,如果另一个并行运行的线程突然决定释放自己的一些内存,这可能会“起作用”。这种情况下的循环将构成一个经典的“忙等待”循环。但即使在这种情况下,这样的代码也几乎没有什么实用价值,原因不止一个。
没有永不。如果malloc
返回 NULL,则表示错误,您可能应该中止。
在不争论为什么或何时有用的情况下,尝试在循环中重新分配可能会起作用,至少在具有 64 位代码和默认页面文件设置的 Windows 上。此外,这可能会购买更多额外的虚拟内存。虽然,不要在无限循环中执行此操作,而是使用有限次数的重试。作为证明,请尝试以下模拟泄漏 1 Mb 内存的代码。您必须在发布版本中运行它,最好不要在调试器下运行。
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 和系统管理页面文件的机器上,我得到以下输出(使用 VS2013 为 x64 目标构建,在 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)。
这不太可能满足您的要求;如果您的内存不足,那么忙于循环直到您获得更多可能会令人失望。您应该只将 NULL 返回给调用程序,以便它可以通过释放不再需要的内存或返回错误来处理资源耗尽。
malloc() 尽力分配内存。如果失败,不要在while循环中重新尝试分配内存(程序可能会永远卡在那里),如果可以的话,尝试释放一些其他进程或线程持有的内存,然后重试。
另一种选择是增加内存,通过在代码本身内部动态增加交换文件或分页内存(但它很危险且不优选)或手动执行。
避免此类问题的最佳方法是计算或估计内存需求,同时编写代码本身。
尝试增加堆大小(为动态分配预留的内存)。
这取决于您的软件是什么以及您的代码的哪一部分受到影响。
首先要知道,当没有可用页面时,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
根据我的经验(UNIX),在网络管理员吹嘘“您想要的新服务器已经存在并准备就绪”之后,立即稳健地处理 malloc 故障是最重要的。一个典型的回复是这样的:“哇,太快了,谢谢”。它在这一点上 malloc 将返回一个 NULL。
最好的办法是抛出一个带有静态错误消息的 std::bad_alloc 异常,该错误消息被捕获并显示在 main() 中。希望您的析构函数进行足够的清理以释放内存,以使 main 中的错误处理不会失败。
如果您使用合理的操作系统和编程风格,OOM 条件是可以恢复的。
您没有正确分配内存 malloc 函数需要参数作为它需要分配的字节数,但是您正在传递字符串的长度,因此 malloc 将根据长度而不是字符串使用的字节保留内存。您应该改用 sizeof() ,如果仍然失败,则 malloc 返回 null ,表示堆栈中没有足够的可用内存来满足您的要求。要正确运行您的代码,请尝试以下操作:-
char* mystrdup(const char *s)
{ char *ab = NULL;
while(ab == NULL) {
ab=(char*)malloc(sizeof(s));
}
strcpy(ab, s);
return ab;
}