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

   C++使用new关键字创建的对象,被分配到堆内存空间,然后得到对象地址,当程序复杂庞大时容易发生访问地址bug或内存泄露bug。为了避免内存泄露并在调试程序时找到内存泄露的bug,可以重载newdelete函数,确保程序的内存安全。

    newdelete关键字都可以作为operator来重载。重载的new函数规定一个参数size_t表示需要在堆空间上分配的内存大小,可以认为等于sizeof得到的大小,这个数值由系统创建对象时传递来。一般的实现方法就是:

[cpp]   view plain copy
  1. #include <stdlib.h>  
  2. void* operator new(size_t type_size)  
  3. {  
  4.   return malloc(type_size);  
  5. }  
[cpp]   view plain copy
  1. #include <stdlib.h>  
  2. void* operator new(size_t type_size)  
  3. {  
  4.   return malloc(type_size);  
  5. }  

    重载的delete函数规定一个参数void*表示要删除的对象地址,一般的实现方法就是:

[cpp]   view plain copy
  1. #include <stdlib.h>  
  2. void operator delete(void* obj_ptr)  
  3. {  
  4.     free(obj_ptr);  
  5. }  
[cpp]   view plain copy
  1. #include <stdlib.h>  
  2. void operator delete(void* obj_ptr)  
  3. {  
  4.     free(obj_ptr);  
  5. }  

    在这两个函数里加入一些调试代码,监视对象的创建和删除,就能比较容易找到bug位置。对于在函数堆栈上按值创建的对象则无需关心,因为函数在堆栈上创建和删除对象不会调用重载的newdelete方法。

    对于查找内存泄露bug,这样做已经可以解决问题,然而我们可以做的更完美一些。考虑一下COM和类似的接口系统,为了正确的引用接口,调用者必须放弃使用newdelete,通过接口的AddRefRelease实现内存安全。然而这样带来的负面影响很大,就是接口必须由实现者完整实现,一旦底层的接口实现被封装,接口类就无法当做普通类来继承,失去了面向对象编程的特性。而基于重载newdelete,我们可以实现一个既可以用new创建,delete删除,又可以用AddRef()引用,Release()释放的类,这个类的基本功能封装之后,可以当做普通的类被继承,而它和继承类都具有以下特性:

    1. 创建者只关心用new来创建,用delete删除,就能保证对象内存安全。

    2. 使用者只需要用AddRef引用它,用Release删除它,也能保证对象内存安全。

    3. 这两种使用方式同时存在仍然可以保证内存安全。

    这个类可以视为一种引用类型,所以命名为reference_type,通过重载newdelete,加上引用计数方法,最终得以实现。

[cpp]   view plain copy
  1. #ifndef __REFERENCE_TYPE_HPP  
  2. #define __REFERENCE_TYPE_HPP  
  3. #include<exception>  
  4. #include<stdlib.h>  
  5. struct __declspec(novtable) reference_type  
  6. {  
  7. private:  
  8.     __int64 __ref_count;  
  9. public:  
  10.     reference_type(): __ref_count(1) { }  
  11.     virtual ~reference_type()  
  12.     {  
  13.         this->__ref_count--;  
  14.     }  
  15.     void _hold()  
  16.     {  
  17.         this->__ref_count++;  
  18.     }  
  19.     void _drop()  
  20.     {  
  21.         this->__ref_count--;  
  22.         if(this->__ref_count <= 0)  
  23.         {  
  24.             delete this;  
  25.         }  
  26.     }  
  27.     void* operator new(size_t obj_size)  
  28.     {  
  29.         return malloc(obj_size);  
  30.     }  
  31.     void* operator new[](size_t arr_size) throw(std::bad_alloc)  
  32.     {  
  33.         throw std::bad_alloc();  
  34.     }  
  35.     void operator delete(void* obj_addr)  
  36.     {  
  37.         reference_type* obj_t = (reference_type*)obj_addr;  
  38.         if(obj_t->__ref_count <= 0)  
  39.         {  
  40.             free(obj_addr);  
  41.         }  
  42.     }  
  43.     void operator delete[](void* arr_addr) throw(std::bad_alloc)  
  44.     {  
  45.         throw std::bad_alloc();  
  46.     }  
  47. };  
  48. #endif  
[cpp]   view plain copy
  1. #ifndef __REFERENCE_TYPE_HPP  
  2. #define __REFERENCE_TYPE_HPP  
  3. #include<exception>  
  4. #include<stdlib.h>  
  5. struct __declspec(novtable) reference_type  
  6. {  
  7. private:  
  8.     __int64 __ref_count;  
  9. public:  
  10.     reference_type(): __ref_count(1) { }  
  11.     virtual ~reference_type()  
  12.     {  
  13.         this->__ref_count--;  
  14.     }  
  15.     void _hold()  
  16.     {  
  17.         this->__ref_count++;  
  18.     }  
  19.     void _drop()  
  20.     {  
  21.         this->__ref_count--;  
  22.         if(this->__ref_count <= 0)  
  23.         {  
  24.             delete this;  
  25.         }  
  26.     }  
  27.     void* operator new(size_t obj_size)  
  28.     {  
  29.         return malloc(obj_size);  
  30.     }  
  31.     void* operator new[](size_t arr_size) throw(std::bad_alloc)  
  32.     {  
  33.         throw std::bad_alloc();  
  34.     }  
  35.     void operator delete(void* obj_addr)  
  36.     {  
  37.         reference_type* obj_t = (reference_type*)obj_addr;  
  38.         if(obj_t->__ref_count <= 0)  
  39.         {  
  40.             free(obj_addr);  
  41.         }  
  42.     }  
  43.     void operator delete[](void* arr_addr) throw(std::bad_alloc)  
  44.     {  
  45.         throw std::bad_alloc();  
  46.     }  
  47. };  
  48. #endif  

    这个对象可以被newdelete,同时也可以被引用和释放,_hold()相当于COM里的AddRef()_drop()相当于COM里的Release()。实现的关键在于重载的newdelete也实现了引用计数功能。当delete函数使用了引用计数时,对象不是一定会被删除,而是直到引用计数归零时被删除,这样就实现了内存安全。最重要的是,这个类可以被继承了,保留面向对象编程的特性。

    对象一旦具有安全引用的功能,就不能按值创建在函数堆栈上了,一旦函数退出自动清掉堆栈,引用就失效,再引用或释放对象就把程序搞崩溃。还有一个新的限制就是,不能创建对象数组!数组中的所有对象值是分配在一个连续的空间,只能一起创建并一起删除,此时引用计数同样全部失效。比如,引用数组某个成员后又用delete[]删除数组,其实被引用的成员也一起删除了,而程序还认为它处于引用状态并访问,最后同样把程序搞崩溃。代码中为了防止这个严重的情况发生,已经重载了new[]delete[],并直接抛出异常。如果需要创建数组,需要实现一个引用安全的数组模板类,这已经跟C#CLI等高级语言相似。

    总的来说,对于reference_type和它的派生类,它们的优点就是使代码规范并让程序内存变安全,而以下列出一些缺点和使用限制,需要在应用中注意:

    1. 不能在函数里创建对象值了,只能用newdelete

    2. 不能创建为对象数组,如果需要使用数组,只能创建为对象指针数组,或者实现一个引用安全的数组模板;

    3. 确保_hold()和_drop()成对使用(当然newdelete也是永远需要成对使用),对象内存就可以安全;

    4. 引用计数功能有极小的性能下降,但多数情况下可以忽略不计;

    5. 这个方法仅限于C++面向对象编程,如果程序分配内存是直接使用C语言的malloc()realloc()free(),则需要其它方法,比如给malloc增加hook函数。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值