[英]How to deal with bad_alloc in C++?
有一个名为foo
的方法有时会返回以下错误:
terminate called after throwing an instance of 'std::bad_alloc'
what(): std::bad_alloc
Abort
有没有办法可以使用try
- catch
块来阻止此错误终止我的程序(我只想返回-1
)?
如果是这样,它的语法是什么?
我还能如何处理 C++ 中的bad_alloc
?
通常,您不能也不应该尝试响应此错误。 bad_alloc
表示由于没有足够的可用内存而无法分配资源。 在大多数情况下,您的程序无法应对这种情况,并且尽快终止是唯一有意义的行为。
更糟糕的是,现代操作系统经常过度分配:在这样的系统上,即使没有足够的可用内存, malloc
和new
也可以返回一个有效的指针std::bad_alloc
永远不会被抛出,或者至少不是一个可靠的标志内存耗尽。 相反,尝试访问分配的内存将导致无法捕获的段错误(您可以处理段错误信号,但之后无法恢复程序)。
捕获std::bad_alloc
时你唯一能做的就是记录错误,并尝试通过释放未完成的资源来确保安全的程序终止(但这是在错误被抛出后的正常堆栈展开过程中自动完成的,如果该程序适当地使用了 RAII)。
在某些情况下,程序可能会尝试释放一些内存并重试,或者使用辅助内存(= 磁盘)而不是 RAM,但这些机会仅存在于具有严格条件的非常特定的场景中:
应用程序控制第 1 点的情况极为罕见——用户空间应用程序永远不会这样做,这是一个系统范围的设置,需要 root 权限才能更改。 1
好的,让我们假设您已经确定了第 1 点。例如,您现在可以做的是对某些数据使用LRU 缓存(可能是一些可以按需重新生成或重新加载的特别大的业务对象)。 接下来,您需要将可能失败的实际逻辑放入支持重试的函数中——换句话说,如果它被中止,您可以重新启动它:
lru_cache<widget> widget_cache;
double perform_operation(int widget_id) {
std::optional<widget> maybe_widget = widget_cache.find_by_id(widget_id);
if (not maybe_widget) {
maybe_widget = widget_cache.store(widget_id, load_widget_from_disk(widget_id));
}
return maybe_widget->frobnicate();
}
…
for (int num_attempts = 0; num_attempts < MAX_NUM_ATTEMPTS; ++num_attempts) {
try {
return perform_operation(widget_id);
} catch (std::bad_alloc const&) {
if (widget_cache.empty()) throw; // memory error elsewhere.
widget_cache.remove_oldest();
}
}
// Handle too many failed attempts here.
但即使在这里,使用std::set_new_handler
而不是处理std::bad_alloc
提供相同的好处,而且会简单得多。
1如果你创建不控制点1的应用程序,你读这个答案,请拍我的电子邮件,我真的很好奇你的情况。
new
的 C++ 标准规定的行为是什么? 通常的概念是,如果new
运算符不能分配请求大小的动态内存,那么它应该抛出类型为std::bad_alloc
的异常。
然而,甚至在抛出bad_alloc
异常之前bad_alloc
发生更多的事情:
C++03 第 3.7.4.1.3 节:说
分配存储失败的分配函数可以调用当前安装的 new_handler(18.4.2.2),如果有的话。 [注意:程序提供的分配函数可以使用 set_new_handler 函数 (18.4.2.3) 获取当前安装的 new_handler 的地址。] 如果使用空异常规范 (15.4) 声明的分配函数 throw() 失败分配存储,它应返回一个空指针。 未能分配存储的任何其他分配函数应仅通过抛出类 std::bad_alloc (18.4.2.1) 或从 std::bad_alloc 派生的类的异常来指示失败。
考虑以下代码示例:
#include <iostream>
#include <cstdlib>
// function to call if operator new can't allocate enough memory or error arises
void outOfMemHandler()
{
std::cerr << "Unable to satisfy request for memory\n";
std::abort();
}
int main()
{
//set the new_handler
std::set_new_handler(outOfMemHandler);
//Request huge memory size, that will cause ::operator new to fail
int *pBigDataArray = new int[100000000L];
return 0;
}
在上面的例子中, operator new
(很可能)将无法为 100,000,000 个整数分配空间,并且函数outOfMemHandler()
将被调用,并且程序将在发出错误消息后中止。
如这里所见,当无法满足内存请求时, new
运算符的默认行为是重复调用new-handler
函数,直到它可以找到足够的内存或没有更多的新处理程序。 在上面的例子中,除非我们调用std::abort()
, outOfMemHandler()
将被重复调用。 因此,处理程序要么确保下一次分配成功,要么注册另一个处理程序,要么不注册处理程序,要么不返回(即终止程序)。 如果没有新的处理程序并且分配失败,则操作员将抛出异常。
new_handler
和set_new_handler
? new_handler
是一个指向一个函数的指针的 typedef,它不接受和返回什么,而set_new_handler
是一个接受并返回一个new_handler
。
就像是:
typedef void (*new_handler)();
new_handler set_new_handler(new_handler p) throw();
set_new_handler 的参数是一个指向函数 operator new
的指针,如果它不能分配请求的内存,它应该调用。 它的返回值是指向先前注册的处理程序函数的指针,如果没有先前的处理程序,则返回 null。
鉴于new
的行为,设计良好的用户程序应该通过提供适当的new_handler
来处理内存不足的情况,它执行以下操作之一:
提供更多可用内存:这可能允许 operator new 循环内的下一次内存分配尝试成功。 实现这一点的一种方法是在程序启动时分配一大块内存,然后在第一次调用 new-handler 时将其释放以供程序使用。
安装不同的 new-handler:如果当前的 new-handler 不能提供更多内存可用,并且有另一个 new-handler 可以,那么当前的 new-handler 可以在它的位置安装另一个 new-handler (通过调用set_new_handler
)。 下一次 operator new 调用 new-handler 函数时,它将获取最近安装的函数。
(这个主题的一个变体是 new-handler 修改自己的行为,所以下次调用它时,它会做一些不同的事情。实现这一点的一种方法是让 new-handler 修改静态的、特定于命名空间的或影响新处理程序行为的全局数据。)
卸载新处理程序:这是通过将空指针传递给set_new_handler
来set_new_handler
。 如果没有安装新的处理程序,当内存分配不成功时, operator new
将抛出异常((可转换为) std::bad_alloc
)。
抛出可转换为std::bad_alloc
的异常。 此类异常不会被operator new
捕获,但会传播到发起内存请求的站点。
不返回:通过调用abort
或exit
。
您可以像其他任何异常一样捕获它:
try {
foo();
}
catch (const std::bad_alloc&) {
return -1;
}
从这一点来看,您可以做什么有用取决于您,但这在技术上绝对是可行的。
我不建议这样做,因为bad_alloc
意味着你的内存不足。 最好是放弃而不是试图恢复。 但是,这是您要求的解决方案:
try {
foo();
} catch ( const std::bad_alloc& e ) {
return -1;
}
我可能会为此建议一个更简单(甚至更快)的解决方案。 如果无法分配内存, new
运算符将返回 null。
int fv() {
T* p = new (std::nothrow) T[1000000];
if (!p) return -1;
do_something(p);
delete p;
return 0;
}
我希望这会有所帮助!
让您的foo 程序以受控方式退出:
#include <stdlib.h> /* exit, EXIT_FAILURE */
try {
foo();
} catch (const std::bad_alloc&) {
exit(EXIT_FAILURE);
}
然后编写一个调用实际程序的shell程序。 因为地址空间是分开的,所以你的 shell 程序的状态总是明确定义的。
当然,您可以捕获bad_alloc
,但我认为更好的问题是首先如何阻止bad_alloc
的发生。
通常, bad_alloc
意味着内存分配出现问题 - 例如,当您内存不足时。 如果您的程序是 32 位的,那么当您尝试分配 >4 GB 时,这已经发生了。 当我将 C 字符串复制到 QString 时,这发生在我身上。 C 字符串不是以 '\\0' 结尾的,这导致strlen
函数返回数十亿的值。 然后它尝试分配几 GB 的 RAM,这导致了bad_alloc
。
当我不小心访问构造函数的初始化列表中的未初始化变量时,我也看到了bad_alloc
。 我有一个带有成员T bar
的类foo
。 在构造函数中,我想用参数中的值初始化成员:
foo::foo(T baz) // <-- mistyped: baz instead of bar
: bar(bar)
{
}
因为我输错了参数,构造函数用它自己初始化了 bar(所以它读取了一个未初始化的值!)而不是参数。
valgrind 对此类错误非常有帮助!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.