Last Updated: 2023-09-27 12:06:24 Wednesday
-- TOC --
C++一般使用new操作符申请内存,当然new也可能会失败,当new失败后,应该如何处理呢?
两种常用的new失败的处理方法
一般情况下,new失败会抛出一个bad_alloc
异常。在《Effect C++》里,提倡这种写法:
#include <cstdio>
#include <new>
using namespace std;
int main(void) {
while (1) {
try {
char* p = new char[1024*1024*1000]{}; //
printf("--1G--%p\n", p);
} catch(std::bad_alloc& e) {
printf("%s\n", e.what());
break;
}
}
return 0;
}
不过,这段代码在Linux上运行,始终看不到异常后的打印,但确实异常了。异常的表现是登录用户自动logout,在tty下也是这样,整个进程树被全部干掉。用nohup运行,将打印输出到文件,也一样,logout后再login,文件空空。另外,还发现new返回很慢,比malloc慢多了,malloc可以成功申请很多很多内存,远大于系统物理内存大小,而且在失败后,能打印出错误,测试进程可以正常退出,不会导致用户logout。
后来同事在Ubuntu虚拟机里面测试,就没有出现logout,只是进程被kill....跟Linux发行版本有关系?
另一种处理new失败的写法,使用std::nothrow
:
#include <cstdio>
#include <new>
using namespace std;
int main(void) {
while (1) {
char *p = new(std::nothrow) char[1024*1024*100]{}; //
if (!p) {
printf("--OOM--\n");
break;
}
printf("100M %p\n", p);
}
return 0;
}
测试这种写法,依然看不到打印,用户直接logout.....但我喜欢这种写法,保持了与C一样的风格。
使用可能throw的new,就无需判断是否为nullptr,使用no throw的new,就要像C语言那样判断返回的指针!在处理前者抛出的异常时,选择处理此异常的位置,可能是个技术活。代码中可能有很多地方都在new,如果每一处都去catch,不仅代码不好看,异常逻辑可能也会出问题。
我们在写Python代码的时候,基本上不会考虑MemoryError,看到过一个建议,仅需要在比较high level的位置,去except这个异常即可。我觉得C++也是一样,不可能在任何使用container的地方,都去catch bad_alloc吧......涉及到内存的代码,真是麻烦呀,不去考虑就很简单,一旦去考虑就发现有些想不清楚,无法处理......一般不吃内存的逻辑,就不太需要去考虑,而特别吃内存的代码,就要仔细考虑斟酌如何处理OOM异常了!
logout的原因
毫无疑问,整个进程树被干掉,一定是申请的内容过多造成的。
经过反复的修改和测试,造成new慢,而且logout问题的原因,是{}
做的初始化!当我把{}
删除后,new的表现就与malloc基本一致,甚至能够申请到的内存比malloc还要多,即更难以跑出来new失败的情况。
如何跑出OOM?(Out Of Memory)
现在都是64位的系统,虚拟内存超级大,物理内存用完了还可以swapping,是难以跑出OOM的。在Linux下,有个简单的方法,可以轻松跑出OOM,就是使用ulimit命令,限制进程的虚拟内存大小。
new和malloc的区别
new 0 is OK
跟malloc(0)
背后的原理一样,下面的代码也是合法的:
#include <iostream>
#include <cstdio>
#include <unistd.h>
using namespace std;
int main(void) {
char* p = new char[0];
printf("%p\n", p);
if (p == nullptr)
cout << "nullptr\n";
delete p;
return 0;
}
申请到了内存,p指向一个有效地址,但是只要写入,就是越界。这个地址,也必须delete。
本文链接:https://cs.pynote.net/sf/c/cpp/202209025/
-- EOF --
-- MORE --