当前位置:编程学习 > 网站相关 >>

重载new和delete方法实现C++内存安全

  C++使用new关键字创建的对象,被分配到堆内存空间,然后得到对象地址,当程序复杂庞大时容易发生访问地址bug或内存泄露bug。为了避免内存泄露并在调试程序时找到内存泄露的bug,可以重载new和delete函数,确保程序的内存安全。
 
    new和delete关键字都可以作为operator来重载。重载的new函数规定一个参数size_t表示需要在堆空间上分配的内存大小,可以认为等于sizeof得到的大小,这个数值由系统创建对象时传递来。一般的实现方法就是:
 
 
#include <stdlib.h>  
void* operator new(size_t type_size)  
{  
  return malloc(type_size);  
}  
    继承的delete函数规定一个参数void*表示要删除的对象地址,一般的实现方法就是:
 
 
[cpp] view plaincopy
#include <stdlib.h>  
void operator delete(void* obj_ptr)  
{  
    free(obj_ptr);  
}  
    在这两个函数里加入一些调试代码,监视对象的创建和删除,就能很快找到bug。对于在函数堆栈上按值创建的对象则无需关心,因为函数在堆栈上创建和删除对象不会调用重载的new和delete方法。
 
    对于修复特定的内存泄露bug,这样做已经足够解决问题,然而我们可以做的更完美一些。考虑一下COM和类似的接口系统,为了正确的引用接口,调用者必须放弃使用new和delete,通过接口的AddRef和Release实现内存安全。然而这样带来的负面影响很大,就是接口必须由实现者完整实现,一旦底层的接口实现被封装,接口类就无法当做普通类来继承,失去了面向对象编程的特性。而基于重载new和delete,我们可以实现一个既可以用new创建,delete删除,又可以用AddRef()引用,Release()释放的类,这个类的基本功能封装之后,可以当做普通的类被继承,而它和继承类都具有以下特性:
 
    1. 创建者只关心用new来创建,用delete删除,就能保证对象内存安全。
 
    2. 使用者只需要用AddRef引用它,用Release删除它,也能保证对象内存安全。
 
    3. 这两种使用方式同时存在仍然可以保证内存安全。
 
    这个类可以视为一种引用类型,所以命名为reference_type,通过重载new和delete,加上引用计数方法,最终得以实现。
 
 
#ifndef __REFERENCE_TYPE_HPP  
#define __REFERENCE_TYPE_HPP  
#include<exception>  
#include<stdlib.h>  
struct __declspec(novtable) reference_type  
{  
private:  
    __int64 __ref_count;  
public:  
    reference_type(): __ref_count(1) { }  
    virtual ~reference_type()  
    {  
        this->__ref_count--;  
    }  
    void _hold()  
    {  
        this->__ref_count++;  
    }  
    void _drop()  
    {  
        this->__ref_count--;  
        if(this->__ref_count <= 0)  
        {  
            delete this;  
        }  
    }  
    void* operator new(size_t obj_size)  
    {  
        return malloc(obj_size);  
    }  
    void* operator new[](size_t arr_size) throw(std::bad_alloc)  
    {  
        throw std::bad_alloc();  
    }  
    void operator delete(void* obj_addr)  
    {  
        reference_type* obj_t = (reference_type*)obj_addr;  
        if(obj_t->__ref_count <= 0)  
        {  
            free(obj_addr);  
        }  
    }  
    void operator delete[](void* arr_addr) throw(std::bad_alloc)  
    {  
        throw std::bad_alloc();  
    }  
};  
#endif  
    这个对象可以被new和delete,同时也可以被引用和释放,_hold()相当于COM里的AddRef(),_drop()相当于COM里的Release()。实现的关键在于重载的new和delete也实现了引用计数功能。当delete函数使用了引用计数时,对象不是一定会被删除,而是直到引用计数归零时被删除,这样就实现了内存安全。最重要的是,这个类可以被继承了,保留面向对象编程的特性。
 
    对象一旦具有安全引用的功能,就不能按值创建在函数堆栈上了,一旦函数退出自动清掉堆栈,引用就失效,再引用或释放对象就把程序搞崩溃。还有一个新的限制就是,不能创建对象数组!数组中的所有对象值是分配在一个连续的空间,只能一起创建并一起删除,此时引用计数同样全部失效。比如,引用数组某个成员后又用delete[]删除数组,其实被引用的成员也一起删除了,而程序还认为它处于引用状态并访问,最后同样把程序搞崩溃。代码中为了防止这个严重的情况发生,已经重载了new[]和delete[],并直接抛出异常。如果需要创建数组,需要实现一个引用安全的数组模板类,这已经跟C#和CLI等高级语言相似。
 
    总的来说,对于reference_type和它的派生类,它们的优点就是使代码规范并让程序内存变安全,而以下列出一些缺点和使用限制,需要在应用中注意:
 
    1. 不能在函数里创建对象值了,只能用new和delete;
 
    2. 不能创建为对象数组,如果需要使用数组,只能创建为对象指针数组,或者实现一个引用安全的数组模板;
 
    3. 确保_hold()和_drop()成对使用(当然new和delete也是永远需要成对使用),对象内存就可以安全;
 
    4. 引用计数功能有极小的性能下降,但多数情况下可以忽略不计;
 
    5. 这个方法仅限于C++面向对象编程,如果程序分配内存是直接使用C语言的malloc(),realloc(),free(),则需要其它方法,比如给malloc增加hook函数。
补充:综合编程 , 安全编程 ,
CopyRight © 2012 站长网 编程知识问答 www.zzzyk.com All Rights Reserved
部份技术文章来自网络,