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

发表于:2012-10-23 09:50

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:lv_ximing    来源:51Testing软件测试网采编

  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*表示要删除的对象地址,一般的实现方法就是:

#include <stdlib.h>
void operator delete(void* obj_ptr)
{
    free(obj_ptr);
}

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

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

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

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

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

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

#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函数。

《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号