如何处理new失败

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的区别

  1. new(和delete)是操作符,malloc(和free)是函数;
  2. new可以调用对象的构造函数,而malloc不可以;
  3. new底层其实是调用了malloc函数;
  4. new可以重载,也可以轻松实现placement new;(C要实现这样的功能,就简单地自己重新定义一个新的函数接口)

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 --