关于C++中内存回收的问题

来源:互联网 发布:淘宝vip福利群 编辑:程序博客网 时间:2024/05/22 11:52

    C++与Java最容易被提起的区别,一个是Java的完全面向对象,一个恐怕就是C++所提供的灵活的操控内存的问题。尽管所有宣扬Java的书籍都说C++的内存管理会让粗心的程序员写出带有许多BUG的程序,但是不可否认,对内存的操作,也是C++的一个迷人的地方。

    前几天开始写一个新的游戏的代码,昨天写了几个小时的资源管理部分,大概的思路就是绘图的线程到资源管理模块中请求资源,如果资源已经被程序载入了,就直接返回资源的Handle(我也不知道这儿可不可以叫做句柄,不过作用是一样的),否则再去硬盘上载入资源。这样绘图线程用到具体图像的时候不需要关心它的加载策略,对于绘图线程而言,每一次用到图像的时候,都可以看做是从硬盘中重新加载。

    问题出现在资源Handle的析构函数上。因为习惯,我在Handle的析构函数中,释放掉了实际储存图像的内存。然后在绘图线程得到Handle之后,因为绘图这边Handle的声明周期结束,系统自动调用析构函数,于是将储存图像的内存也释放掉了,但是资源管理模块中并没有顾及到这一点,仍然将这个资源当做已经载入过来处理,于是下一次再通过Handle来渲染的时候,就发生了内存访问错误,也就是所谓的野指针。

    这段代码写的时候,也就用了三个小时,其中还涉及到了一个特殊的资源格式的解码,将它转换成我所用的引擎支持的32位颜色存进内存。但是为了找到这个BUG,我却花了整整5个小时,最后也是因为灵机一动,才定位到这个错误。

    Handle这个东西,其实跟C++的指针很类似,它的声明周期结束的时候,并不会释放它所指的内存,如果这个时候没有delete操作,就存在了内存泄露。相反如果提前执行了delete,就会发生野指针的问题。所以今天特意花些时间总结一下。

  1. 指针已经delete过之后又一次delete。很不幸的是,在C++中,尽管已经对一个指针执行了delete操作,系统也只会收回指针所指向的内存,而指针的值并没有被写回NULL。这样一些新手的程序员就容易写成如下的代码
    int* p = new int[10];delete p;//something else//....if( p != NULL)  delete p;

    这段代码从语义上当然没有问题,如果第一个delete在某个分支语句中,那么后面的delete操作就必不可少了。但是正如前面所说的,尽管已经释放了指针所指的内存,第二个delete操作也仍然会被执行到。所以,delete之后应该紧跟着一句代码来将指针重新赋成NULL,没有例外。
  2. 指针的作用有两点,一个是可以动态的申请内存,还有一个就是允许跨越函数中的声明周期限制来传递数据。这在尽量减少全局变量的原则下是很重要的特性,但是由此带来的一个很严重的问题就是如下的代码:
    void Function1(){   int* p;   Function2(p);}void Function2(int* p){    //Something to do with p.}

    这样,p就没有进行过内存分配。如果在大型工程中,这两个函数分别在不同的文件中,那么程序员也很难保证不遗漏指针的new操作。所以对于在一段生命周期明显的代码段中,第一次使用指针,应该对指针进行是否为NULL的检查。同时,应在声明指针的时候就对其进行内存分配和初始化。如果确实不应该分配内存,应将其设为NULL。

总结:

  1. 对于所有类似指针的Handle,释放内存应该显式地调用Release()函数,而不是仅仅在对象析构函数中释放。
  2. 对于类中的指针,应该在析构函数中释放内存,否则即会发生内存泄露。
  3. delete之后应该紧跟着一句代码来将指针重新赋成NULL,没有例外。
  4. 所以对于在一段生命周期明显的代码段中,第一次使用指针,应该对指针进行是否为NULL的检查。
  5. 应在声明指针的时候就对其进行内存分配和初始化。如果确实不应该分配内存,应将其设为NULL。