Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1475124
  • 博文数量: 150
  • 博客积分: 65
  • 博客等级: 民兵
  • 技术积分: 3415
  • 用 户 组: 普通用户
  • 注册时间: 2011-02-25 10:30
个人简介

游戏后台开发

文章分类

全部博文(150)

文章存档

2020年(1)

2019年(4)

2017年(3)

2016年(6)

2015年(4)

2014年(45)

2013年(86)

2012年(1)

分类: C/C++

2014-01-15 16:14:07

在C++语言中,我们经常会使用new给一个对象分配内存空间,而当内存不够会出现内存不足的情况。C++提供了两中报告方式:

  1、抛出bad_alloc异常来报告分配失败;

  2、返回空指针,而不会抛出异常。

  C++为什么会采用这两种方式呢?这主要是由于各大编译器公司设计C++编译器公司的结果,因为标准C++是提供了异常机制的。例如,VC++6.0中当new分配内存失败时会返回空指针,而不会抛出异常。而gcc的编译器对于C++标准支持比较好,所以当new分配内存失败时会抛出异常。

  究竟为什么会出现这种情况呢?

  首先,C++是在C语言的基础之上发展而来,而且C++发明时是想尽可能的与C语言兼容。而C语言是一种没有异常机制的语言,所以C++应该会提供一种没有异常机制的new分配内存失败报告机制;(确实是如此,早期的C++还没有加入异常机制)

  其次在返回空指针的实现过程中,C++采用的是malloc/calloc 等分配内存的函数,该类函数不会抛出异常,但是在分配内存失败时会返回“空指针”。

  最后,对于标准的C++,有着比较完善的异常处理机制,所以对于出现异常时,会抛出响应的异常。对于new分配失败时,系统会抛出bad_alloc异常。

  鉴于以上原因,我们在不同的编译器需要new分配失败时做不同的处理。例如:

  情况1:


int* p = new int(5);
if ( p == 0 ) // 检查 p 是否空指针
    return -1;
...


  情况2:


try {
      int* p = new int(5);
        // 其它代码
} catch ( const bad_alloc& e ) {
      return -1;
}


  情况1和情况2的代码都是对于new失败时的处理,而针对不同的编译器,可以这种处理会完全失效。如果在gcc编译器采用情况1,那么if(p==0)完全是没有意义的,因为不管new内存分配成功失败与否,都不会出现p=0的情况。即,如果分配成功,p=0完全不可能;而分配失败,new会抛出异常跳过其后面的代码。而需要采用情况2的处理方式,即应该来捕捉异常。

  同样,如果在VC++6.0中采用情况2的代码,那么new分配失败时,完全不会抛出异常,那么捕捉异常也是徒劳的。

  所以在new分配内存的异常处理时要特别小心,可以两种方式联合使用,来解决跨平台跨编译器的难题。

  当然情况2中的异常处理代码是最简单的处理方式,下面我们为其制定一个客户制定的错误处理函数,即new-handler。



typedef void (*new_handler)();
new_handler set_new_handler(new_handler p) throw();




这里首先定义new-handler函数指针类型,然后定义一个new-handler函数set_new_handler,其参数是指向operator new无法分配足够内存时应该被调用的函数。其返回指也是一个指针,指向set_new_handler被调用前正在执行(但是马上就要被替换)的那个new-handler函数。下面设计一个当operator new无法分配足够内存时应该被调用的函数:


void noMemoryToAlloc()
{
       std::cerr << "unable to satisfy request for memory\n";

        std::abort();
}


  使用noMemoryToAlloc函数的代码为:


int main()
{
       set_new_handler(nomorememory);
       int *pArray = new int[100000000000000L];

...

}


  当operator new无法分配足够空间时,noMemoryToAlloc就会被调用,于是程序就会发出一个错误信息cerr之后,调用abort函数结束程序。

  如果operator new无法分配足够空间时,我们希望不断调用new-handler函数,直到找到足够内存为止,那么我们的operator new函数就可以设计为:


void *operator new(std::size_t size) throw(std::bad_alloc)
{
        if ( size==0 ) {
             size  = 1;
        }
       while (true) {
            调用malloc等内存分配函数来尝试分配size大小的内存;
            if ( 分配成功 )
                 return 指向分配得来的内存指针;
            new_handler globalHandler  = set_new_handler(0);
            set_new_handle(globalHandler);
            if(globalHandler)
                   (*globalHandler)();
           else
                   throw std::bad_alloc();
       }
}




 使用new分配内存失败时往往会使用asert()终止程序,但是这只能在除错模式下abert函数才能有效,在生产模式下,abert只是一个void指令,所以连程序都跳不出来。

    而当new操作失败时,一个好的程序不能简单的终止程序就行了,而是要尝试去释放内存
    如何能在new操作失败,在抛出异常之前先把相应的处理做了呢?这就要用到new_handler了,它是在抛出exception调用的。为了指定这个所谓的“内存不足处理函数,new_handler”,client必须调用set_new_handler,这是头文件提供的函数,用法:
    typedef void (*new_handler)()
    new_handler set_new_handler(new_handler p) throw();
简单的程序测试:
#include "stdafx.h"
#include
#include
using namespace std;

void NoMoreMemory()
{
cout<<"Unable to statisty request for memory"<
abort();
}

int _tmain(int argc, _TCHAR* argv[])
{
set_new_handler(NoMoreMemory);
int *p=new int[536870911];
return 0;
}
注:当operator new 无法满足内存需求时,它会不只一次地调用new_handler函数(如果new_handler没有退出程序的话);它会不断地调用,直到找到足够的内存为止。因此一个良好设计的new_handler函数必须完成下列事情之一:
1)让更多的内存可用。这或许能够让operator new 的下一次内存配置行为成功。实现此策略的方法之一就是在程序起始时配置一大块内存,然后在new_handler第一次被调用时释放之。如此的释放动作常常伴随着某种警告信息,告诉用户目前的内存已经处于低水位,再来的内存需求可能失败,除非有更多的内存回复自由身。
2)安装一个不同的new_handler,如果目前的new_handler无法让更多的内存可用,或许它知道另一个new_handler手上我有比较多的资源。果真如此,目前的new_handler就可以安装另外一个new_handler以取代自己(只要再调用一次set_new_handler即可)。当operator new下次调用new_handler函数时,它会调用最新安装的那个。
3)卸除掉这个new_handler,也就是说,将null指针传给set_new_handler,一旦没有安装任何new_handler,operator new就会在内存配置失败时抛出一个类型为std::bad_alloc的exception。
4)抛出一个exception,类型为std::bad_alloc(或者派生类型)。这样的exception不会被operator new捕获,所以它们就会传动到最初踢出内存需求的那个点上
5)不返回,直接调用abort或exit
阅读(5264) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~