在C++程序开发中,动态内存分配是非常常见的操作,我们使用new关键字从堆上申请内存空间。但内存资源是有限的,当系统可用内存不足,或者申请的内存大小超出系统限制时,new操作就会失败。如果开发者没有针对这种失败情况设计对应的处理策略,程序很可能会出现未定义行为甚至直接崩溃,因此掌握new失败时的异常处理方法对写出健壮的C++程序至关重要。

new操作的默认异常处理行为
在C++标准定义中,普通的new表达式在分配内存失败时会抛出std::bad_alloc类型的异常。如果程序中没有捕获这个异常,异常会沿着调用栈向上传播,最终如果没有被处理,程序会调用std::terminate终止运行。
我们可以通过下面的代码示例观察默认行为:
#include <iostream>
#include <new>
#include <cstring>
int main() {
try {
// 尝试申请一个极大的内存空间,大概率会失败
long long* huge_mem = new long long[1000000000000];
std::cout << "内存分配成功" << std::endl;
delete[] huge_mem;
} catch (const std::bad_alloc& e) {
// 捕获new抛出的bad_alloc异常
std::cout << "捕获到内存分配异常: " << e.what() << std::endl;
}
return 0;
}
上述代码中,我们使用try-catch块捕获了std::bad_alloc异常,当new分配失败时,程序不会崩溃,而是会执行catch块中的逻辑,输出异常信息。
使用nothrow版本的new
如果开发者不希望使用异常机制处理new失败的情况,可以使用nothrow版本的new,它的声明在<new>头文件中。nothrow版本的new在分配失败时会返回空指针,而不是抛出异常,开发者可以通过判断返回值是否为nullptr来确定分配是否成功。
对应的使用示例如下:
#include <iostream>
#include <new>
int main() {
// 使用nothrow版本的new,失败时返回nullptr
int* mem = new (std::nothrow) int[1000000000];
if (mem == nullptr) {
std::cout << "内存分配失败,返回了空指针" << std::endl;
} else {
std::cout << "内存分配成功" << std::endl;
delete[] mem;
}
return 0;
}
这种方式适合不希望引入异常处理的场景,但是需要注意,所有使用nothrow new的地方都需要手动判断返回值,否则很容易出现空指针解引用的问题。
自定义new_handler处理全局new失败
当new分配内存失败抛出异常之前,会先调用一个全局的new_handler函数,我们可以通过std::set_new_handler来设置自定义的new_handler。new_handler的函数原型是void(*)(),也就是无参数无返回值的函数指针。
自定义new_handler的典型用途包括:释放一些不必要的缓存来腾出内存、记录内存分配失败的日志、或者直接终止程序。下面是一个自定义new_handler的示例:
#include <iostream>
#include <new>
#include <cstdlib>
// 自定义的new_handler函数
void custom_new_handler() {
std::cout << "全局new分配内存失败,执行自定义处理逻辑" << std::endl;
// 这里可以选择释放缓存、记录日志等操作
// 如果无法处理,可以选择终止程序
std::abort();
}
int main() {
// 设置自定义的new_handler
std::set_new_handler(custom_new_handler);
try {
// 尝试分配超大内存
long long* mem = new long long[1000000000000];
delete[] mem;
} catch (const std::bad_alloc& e) {
std::cout << "捕获到异常: " << e.what() << std::endl;
}
return 0;
}
需要注意的是,new_handler函数可以反复被调用,直到内存分配成功或者程序被终止。如果new_handler函数没有终止程序也没有成功腾出内存,可能会导致死循环,因此设计new_handler时需要谨慎。
不同处理策略的适用场景
我们可以根据项目的实际需求选择合适的new失败处理策略:
- 如果项目已经广泛使用异常机制,那么使用默认的new异常抛出,配合try-catch捕获处理是最规范的做法,符合C++的标准设计理念。
- 如果项目禁止使用异常,或者是在一些对异常支持不完善的嵌入式环境中开发,那么nothrow版本的new是更合适的选择,只需要每次分配后判断返回值即可。
- 如果希望对所有new失败的情况做统一的全局处理,比如记录日志、统一释放缓存,那么自定义new_handler是更好的方案,不需要在每个new调用的地方都写重复的处理逻辑。
注意事项
在处理new失败的场景时,有几个常见的误区需要避免:
不要混淆普通的new和new[],两者的失败处理逻辑是一致的,new[]失败同样会抛出std::bad_alloc异常,或者nothrow版本返回nullptr。另外,不要忘记匹配的内存释放操作,new分配的内存需要用delete释放,new[]分配的内存需要用delete[]释放,否则会导致内存泄漏或者未定义行为。如果使用了自定义的new重载,也需要对应实现自定义的delete重载,保证内存管理的一致性。
最后,即使做了完善的new失败处理,也应该尽量避免频繁申请超大块的内存,合理规划内存使用,从源头减少内存分配失败的概率,才是提升程序稳定性的根本方法。