C++学习笔记--new失败后的处理

众所周知,C++中使用new关键字申请内存成功时会返回申请的内存起始地址,并在该地址上调用构造函数。那么,有考虑过失败的情况吗?

动态申请的处理方式

  • C语言
int *p = (int*)malloc(10000000000000000000);
if(NULL == p)
{
	//申请失败处理分支
}
else
{
	//申请成功处理分支
	//.....
	free(p);
}
  • C++
int* p = new[10000000000000000000];
if(NULL == p)
{
	//申请失败处理分支
}
else
{
	//申请成功分支
	//.....
	delete [] p;
}

动态申请的结果

C语言

  • 成功:返回申请成功的内存起始地址
  • 失败:返回NULL

C++

  • 成功:返回申请成功的内存起始地址
  • 失败:返回NULL或抛出std::bad_alloc异常,结果视编译器的不同而不同。
  • 这是因为在早期的C++编译器中为了兼容C编译器的方式而做出的行为,而在近代,C++由于支持抛出异常来处理一些可预见的异常情况后,C++编译器就将抛出std::bad-alloc异常作为new失败的行为,so,当申请失败抛出异常时程序就不会执行到申请失败的处理分支中。

new中的异常是怎么抛出 异常的?

  • new关键字在空间不足导致内存分配失败时
    • 会调用全局的new_handler类型的函数(包含在头文件中)来处理调用失败的情形,这样就可能在new_handler函数中有机会腾出足够的空间使得可以申请足够的空间,但C++编译器不知道具体怎么操作,所以就有了个默认处理,即在new_handler函数里抛出异常std::bad_alloc异常。
    • new_handler的类型是:void (*new_handler)(),它是一个函数指针。
    • 由于编译器不知道如何整理内存 碎片,所以就可以自定义new_handler函数在不同环境中做不同的内存整理工作。

自定义new_handler函数及使用

  • new_handler函数定义,函数类型需要和new_handler类型一样(即void(*)()类型),具体实现一般在工程中是整理内存碎片,也可以在此处抛出std::bad_alloc异常,由于是学习,所以使用粗暴的方式进行处理:
void my_new_handler()
{
	cout << "No enough memory" << endl;
	exit(1);
}
  • new_handler函数使用,使用new_handler set_new_handler(new_handler)函数设置在new失败时需要调用的new_handler函数
  • set_new_handler的输入参数是operator new分配内存失败时要调用的出错处理函数的指针(此处的my_new_handler),返回值是set_new_handler没调用之前就已经在起作用的旧的出错处理函数的指针(编译器默认的全局new_handler函数)。
void ex_func()
{
	new_handler func = set_new_handler(my_new_handler);
	if(func)//为何判断,因为有些编译器
	{
		func();
	}
}

由于编译器不同,如何进行行为统一?

  • 全局范围(风险太大,不推荐)
    • 重新定义new/delete的实现(操作符重载),不抛出任何异常
    • 自定义不抛出异常的new_handler函数,并设置给编译器处理申请失败的情形
  • 类层次范围
    • 重载new/delete函数,不抛出异常
  • 单次动态内存分配
    • 在new时使用nothrow参数,指明不抛出异常,此时返回空指针

new使用实例

首先进行new/delete关键字的重载:

class Test
{
    int m_value;
public:
    Test()
    {
        cout << "Test()" << endl;
        m_value = 0;
    }
    
    ~Test()
    {
        cout << "~Test()" << endl;  
    }
    
    void* operator new (size_t size) //throw()
    {
        cout << "operator new: " << size << endl;
        return NULL;//模拟new失败的情形
    }
    
    void operator delete (void* p)
    {
        cout << "operator delete: " << p << endl;
        free(p);
    }
    
    void* operator new[] (size_t size) throw()
    {
        cout << "operator new[]: " << size << endl;
        return NULL;//模拟new失败的情形
    }
    
    void operator delete[] (void* p)
    {
        cout << "operator delete[]: " << p << endl;
        free(p);
    }
};
实例一:证明编译器是否存在全局的new_handler处理函数
void my_new_handler()
{
	cout << "No enough memory" << endl;
	exit(1);
}
void ex_func_1()//证明编译器是否带有全局的new_handler处理函数
{
	new_handler func = set_new_handler(my_new_handler);
    try
    {
        cout << "func = " << func << endl;
        if( func )//为何判断,因为存在一些编译器不含全局的new_handler函数
        {
            func();
        }
    }
    catch(const bad_alloc&)//证明是否会抛出bad_alloc异常
    {
        cout << "catch(const bad_alloc&)" << endl;
    }
}
  • g++执行结果
    -
  • VS2010执行结果
    -
  • bcc编译器执行结果
    -

根据上述结果可知,不同的编译器确实存在差异,三款编译器中只有bcc编译器定义了全局的默认new_handler函数,并在new失败时抛出异常。

统一编译器的行为:不抛出异常

  • 在重载的new/delete中添加throw()声明告诉编译器不抛出异常
    void* operator new (size_t size) throw()//声明后就只返回空指针,不抛出异常
    {
        cout << "operator new: " << size << endl;
        return NULL;
    }
  • 使用new(nothrow) Test();//使用nothrow关键字告诉编译器不抛出异常
    int* p = new(nothrow) int[10];//在指定空间申请内存并构造对象
    // ... ...
    delete[] p; 
  • new关键字在指定地址上分配内存
int bb[2] = {0};
    
    struct ST
    {
        int x;
        int y;
    };
    
    ST* pt = new(bb) ST();//指明在bb地址上创建对象
    
    pt->x = 1;
    pt->y = 2;
    
    cout << bb[0] << endl;//通过打印值证明是在bb上创建了对象
    cout << bb[1] << endl;
    
    pt->~ST();//在地址上指明调用构造函数后需要手动调用析构函数

在这里插入图片描述

当使用new申请一块内存失败时,抛出异常std::bad_alloc是C++标准中规定的标准行为,所以推荐使用try{ p = new int[10000000]; } catch( std::bad_alloc ) { … }的处理方式。但是在一些老旧的编译器中,却不支持该标准,它会返回NULL,此时具有C传统的判断申请结果形式代码便起了作用。所以,要针对不同的情形采取合理的处置方式,在工程中为了增强可移植性可以采取相应方式(不抛出异常、new_handler函数处理)来统一new失败的行为。
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值