如果第一次分配内存失败,是否应该重新尝试分配内存?

3
我希望编写的代码在一定程度上能够抵御内存溢出。我的解决方案是,如果任何内存分配失败,程序将在那一点暂停执行,并询问操作员是否可以尝试释放一些内存后重新尝试分配(或者,如果证明不可能,他们可以选择自行终止程序,取决于他们)。
但是目前为止,我编写的代码似乎非常丑陋。以下是在任何std::vector操作之前执行的代码块:
while(pointVector.size() == pointVector.capacity){
    // will not break past this if the while statement remains true
    // ERROR.report() has the power to kill the program if it needs to
    try{
        pointVector.reserve(pointVector.capacity * 2); // edited
    }catch(...){
        ERROR.report(Error::Severity::Memory
                    , __LINE__, __FILE__
                    , "Failed to allocate enough points"
                    , pointVector.size(), 0, 0);
    }
}

pointVector.push_back(point);

ERROR对象是专门预先分配其所有资源的,因此它可以向操作符询问而不会引起任何新问题(理论上)。我的问题是,是否有更好的形式可以采取?C++是否具有针对这种情况的“重试”逻辑?还是这基本上就是该怎么做的?


1
使用自定义分配器来为您的向量分配内存,这样您可以将逻辑包装在分配器中,而无需在代码中到处添加“reserve”、“try/catch”等内容。 - syam
@delnan - 实际上,它每次只扩展32个单位。对于这个特定向量,有很多点,成千上万个。浪费空间的最坏情况只有31个点,我认为这并不是很糟糕。 - Anne Quinn
@syam - ...就是这样吗?哇,谢谢!我终于可以使用向量和映射而不会有try/catch块阻塞一切了(您的评论似乎已经消失了,但感谢您提供的简短示例)。 - Anne Quinn
2
@Clairvoire 这里是一个正确的版本,使用了C++11的aligned_storage。对于之前的混淆,我很抱歉。 - syam
2
我没有用过,但你可以尝试使用 std::set_new_handler 来设置一个回调函数,在重试分配或允许其失败之前,请求更多的内存。 - Blastfurnace
显示剩余6条评论
5个回答

3

通常情况下,C++没有内置的方法来解决这个问题。程序运行时耗尽内存通常是编程语言无法容忍的,因此它将失败,假设没有所需的内存就无法继续执行功能。由于没有内在的“重试”逻辑,你只能使用类似于现有的方法。很抱歉,在这里没有特别干净的方法。


2
根据“你只需要支付你所使用的”的原则,C++不会自己进行“重复”或“再试一次”的操作。当然,在这种情况下,是否值得再试一次是另外一个问题……除非你拥有正在运行某些关键系统并且低内存操作是你必须处理的情况的系统,否则我认为再试一次可能比退出更糟糕。您还可以检查错误是否是“std :: bad_alloc”[或称为“无法分配”的其他内容]之外的其他错误,因为如果存在其他错误,则重试可能非常无意义。你应该为重复循环的次数设置限制(以防万一ERROR.report()中有bug)。按32增长是可以的,只要您的项目列表相对较小 - 但我曾经见过几乎与此代码完全相同的代码引起问题,因为通过每次“仅”增加32个到总大小为32MB,数据被复制了无数次,使应用程序看起来像已挂起,从而得到了用户的错误报告。将其更改为几何增长(每次加倍)修复了特定错误。

我在考虑将+32改为*2。我想我应该继续这样做。我只添加了这个限制,因为我担心push_back()每次只会将数组扩展1个单位,这将是相当令人不可思议的。 - Anne Quinn
1
push_back() 函数(几乎)保证会按 2 * size() 的大小增长,因为 vector 保证添加单个元素的平均复杂度为 O(1)。如果每次只增加一个元素,则平均复杂度将变为 O(n),这不符合 C++ 标准的规定。 - Mats Petersson
我明白了,谢谢你告诉我!我相信我可以重写这个函数来解决这个问题,也许跳过第一个比较,只看看 push_back() 是否抛出异常,但现在,我会先修复这个问题,使块模拟 push_back() 如何进行分配。 - Anne Quinn

2

除非您在问题中讨论的确实有非常特殊的要求,否则如果应用程序的内存需求未得到满足,它退出是合理的:让用户释放内存并再次运行您的程序。这避免了编写在99.9%的情况下都不需要的额外代码,并且在这种情况下,这可能会极大地影响性能。

例如,通常push_back在平摊常数时间内运行,但由于容器大小线性增加,您的“grow + pushback”组合实际上将以线性时间运行。这个巨大的性能下降将影响所有用户,而只为那些体验内存耗尽的小部分用户提供好处。


该程序是一个游戏。从严格意义上讲并不像生死攸关,但最好在崩溃之前让用户知道问题出在哪里,或者给他们纠正错误的机会。(错误具有足够预先分配的开销,如果需要的话它可以“尝试”保存游戏状态并重新加载所有内容,以便不会丢失进度)。编辑:看起来我关于push_back()的想法是错误的,我编辑了问题,以便块反映push_back()否则将分配内存的方式。 - Anne Quinn

1

你要查找的函数是std::set_new_handler(new_handler new_p)

它允许你指定一个方法,如果分配内存即将失败,由于程序已经耗尽了内存。该方法可以释放一些内存,以便实现分配成功。如果该方法能够释放一些内存,则应返回true,否则必须抛出bad_alloc异常或终止程序。(有关set_new_handler的文档具有更具体的信息。)

使用set_new_handler的一个好处是,您就不必在每个new调用中包含try/catch块,以确保您没有耗尽内存。


0

我认为你最好的方法是在内存池中分配内存,并从那里使用内存,这样你将完全控制内存,并且可以进行一些清理工作,如碎片整理,如果你用尽了内存。按照你提出的恢复方式,我认为只会拖延不可避免的结果。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接