C++ 6 资源分配和释放

来源:互联网 发布:阿里云 代码托管 编辑:程序博客网 时间:2024/05/17 02:30

6 资源分配和释放

原则6.1 明确产品动态内存的申请与释放原则

说明:之所以存在大量内存问题,主要原因是申请与释放内存的规则混乱:

 申请内存后,传入子程序中,由子程序使用并释放;

 由子程序申请内存并返回父程序,层层调用后在某一个函数内释放。
内存申请与释放一般原则:

 对象在退出其作用域时,就应该立即被释放,而且要做到:谁申请,谁释放。

 函数内分配的内存, 函数退出之前要释放,避免跨函数释放;

 类中数据成员的内存,在析构函数中确认并释放;

 全局变量、静态变量的内存空间则在进程退出时,或相应的共享库被卸载时,由操作系统回收;

 如果程序分支很多或内存资源的分配与释放不在同一个地方,要考虑使用RAII等资源跟踪管理技

    术。

规则6.1 明确operator new的行为和检查策略

说明:当operator new无法满足某一内存分配需求时,默认会抛异常,也可以返回空指针(通过编译选

项设置)。团队明确operator new的操作。在申请内存后,要立即检查指针是否为NULL或进行异常处理。

示例:捕获异常来处理申请内存失败情况

    char* pBuffer = NULL;
    try
    {
       pBuffer = new char[BUFFER_SIZE];
    }
    catch (...)
    {
       pBuffer = NULL;
       return EF_FAIL;
    }
或进行非空判断:

    char* pBuffer = new char[BUFFER_SIZE];
    if (NULL == pBuffer)
    {
       return ER_FAIL;
    }

规则6.2 释放内存后,要立即将指针设置为NULL,防止产生野指针

说明: free或delete释放内存后,立即将指针设置为NULL,防止产生“野指针”。这种判断最好能够

封装起来,见建议6.2。

示例:

    char* pBuffer = new char[BUFFER_SIZE];
    //…
    delete [] pBuffer;
    pBuffer = NULL;

规则6.3 单个对象释放使用delete,数组对象释放使用delete []

说明:单个对象删除使用delete, 数组对象删除使用delete [],原因:

调用new所包含的动作:从系统中申请一块内存,若是对象调用相应的构造函数。

调用new[n]所包含的动作:申请可容纳n个对象外加一点内存来保存数组的元素的数量;调用n次构造

函数初始化这块内存中的n个对象。

调用delete所包含的动作:若是对象调用相应的析构函数;将内存归还系统。

调用delete[]所包含的动作:从new[]将找出的n值;调用n次相应的析构函数;将内存归还给系统。

示例:

    std::string *string = new std::string;
    std::string *stringArray = new std::string[100];

    delete string;
    string = NULL;

    delete [] stringArray;
    stringArray = NULL;
如果使用delete stringArray;会导致stringArray指向的100个string对象中的99个未被销毁,因为它们

的析构函数根本没有被调用,并且结果是不可预测的,编译器不同,结果也不同。

规则6.4 释放结构(类)指针时,首先释放其成员指针的内存空间

示例:下面是一段有内存泄漏的产品代码:

    struct STORE_BUF_S
    {
       ULONG   ulLen;
       UCHAR   *pcData;
    }STORE_BUF_T;

    void func()
    {
       STORE_BUF_T *pstStorageBuff = NULL;
       //申请结构内存….
       //程序处理…
       free(pstStorageBuff);
       return;
    }
先删除了pstStorageBuff,pstStorageBuff->pcData永远不可能被删除了。删除结构指针时,必须从底层

向上层顺序删除。即:

    free (pstStorageBuff->pcData);
    free(pstStorageBuff);

规则6.5 释放指针数组时,首先释放数组每个元素指针的内存

说明:在释放指针数组时,确保数组中的每个元素指针是否已经被释放了,这样才不会导致内存泄漏。

示例:

    struct dirent **namelist;
    int n = scandir(path.c_str(), &namelist, 0, alphasort);// 【1】

    int i = 0;
    for(i ; i < n; ++i)
    {
       string name = namelist[i]->d_name;
       free(namelist[i]); // 【2】
       if(name != ".." && name != ".")
        {
           //.........
           ++fileNum;
           if(MAX_SCAN_FILE_NUM == fileNum )//MAX_SCAN_FILE_NUM=1000
           {
              break;
           }
        }
    }
    free(namelist); // 【3】
    return ;
从上面的代码可以看是指针数组namelist由系统函数进行分配内存(如【1】所示),内存释放时时分别
由【2】释放数组单个元素和 【3】释放数组本身内存一起完成的。

但是中间有个条件,每次只取1000个文件,如果目录下的文件大于1000就跳出,后面的就不会再处理

(【2】没有执行到)。当本地目录下文件数比较小,小于等于1000时没有内存泄漏;而当本地目录下的

文件大于1000时,就会导致内存泄漏。所以释放指针数组时,请注意首先释放其每个元素的内存空间。

正确的做法是在【3】之前加上:

    for(int j = i ; j < n; ++j)
    {
        free(namelist[i]);
    }

规则6.6 不要返回局部对象指针

说明:局部对象在定义点构造,在同一作用域结束时立即被销毁。

示例:

    char* GetParameter ()
    {
        CDBConnect DBConnect;
        //………………..
        return DBConnect.GetString("ParamValue");
    }
由于对象DBConnect已经析构,对应的指针已经被释放,从而后续访问非法内存,导致系统coredump。

规则6.7 不要强制关闭线程

说明:线程被强制关闭,导致线程内部资源泄漏。用事件或信号量通知线程,确保线程调用自身的退

出函数。线程死锁需要强制关闭的情况除外。

示例:强制关闭线程,导致线程资源泄漏。

    CShakeHand::CShakeHand()
    {
       m_hdShakeThreadrecv = CreateThread(NULL, 0,
           (LPTHREAD_START_ROUTINE)ThreadProc_ShakeHands,
           this, NULL, &m_ulShakeThreadID);
    }
    CShakeHand::~CShakeHand()
    {
        TerminateThread(m_hdShakeThreadrecv, 0); //强制关闭
        CloseHandle(m_hdShakeThreadrecv);
    }

建议6.1 使用new, delete的封装方式来分配与释放内存

说明:推荐使用如下宏,可以在一定程度上避免使用空指针,野指针的问题。

    #define HW_NEW(var, classname) \
        do { \
        try \
    { \
       var = new classname; \
    } \
        catch (...) \
    { \
       var = NULL; \
    } \
       break; \
        } while(0)
//(1)该宏会将var置为NULL, 所以调用该宏之后, 不再需要置var为NULL
    //(2) HW_DELETE宏与NEW对应, 用来释放由HW_NEW分配的对象
    //   注意: 如果以数组方式分配对象(见对HW_NEW的描述), 则必须使用宏HW_DELETE_A
    //   来释放, 否则可能导致问题,参见:规则6.3
    #define HW_DELETE(var) \
       do \
    { \
       if (var != NULL) \
    { \
       delete var; \
       var = NULL; \
    } \
       break; \
    } while(NULL == var)

    //(1)这个宏用来删除一个由HW_NEW分配的数组, 删除之后也会将var置为NULL
    #define HW_DELETE_A(var) \
       do \
    { \
       if (var != NULL) \
    { \
       delete []var; \
       var = NULL; \
    } \
       break; \
    } while(NULL == var)
直接使用HW_DELETE,HW_DELETE_A宏来释放指针内存空间,就不会出现遗忘将指针置为NULL

了。

建议6.2 避免在不同的模块中分配和释放内存

说明:在一个模块中分配内存,却在另一个模块中释放它,会使这两个模块之间产生远距离的依赖,

使程序变得脆弱。

模块在C++是一个不清晰的概念,小到一个类,大到一个库。如果在不同的类之间分配、释放内存,需

要考虑两个类的初始化、销毁顺序;如果在不同的库之间分配、释放内存,需要考虑两个库的加载或

卸载顺序。这种远距离的依赖,容易导致遗漏和重复操作,引发严重问题。

有时,在通信机制下,两个实体(如线程)之间交换数据或消息,考虑到程序执行效率,不会采用拷贝

的方式交换数据,而是通过指针交换数据,仍然会在不同位置分配、释放内存。这种情况下,只有数

据交换成功以后,才会由对方负责释放,否则应遵循“谁申请、谁释放”的原则。为了降低处理复杂

性,可以适当地采用RAII或智能指针。

建议6.3 使用 RAII 特性来帮助追踪动态分配

说明:RAII是“资源获取就是初始化”的缩语(Resource Acquisition Is Initialization),是一种

利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。

RAII 的一般做法是这样的:在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内

始终保持有效,最后在对象析构的时候释放资源。这种做法有两大好处:

我们不需要显式地释放资源。

对象所需的资源在其生命期内始终保持有效。这样,就不必检查资源有效性的问题,可以简化逻辑、

提高效率。
C++类库的智能指针就是其中之一:

auto_ptr是标准C++库提供的一种模板类,使用指针进行初始化,其访问方式也和指针一样。在auto_ptr

退出作用域时,所指对象能被隐式的自动删除。这样可以象使用普通指针一样使用auto_ptr,而不用

考虑释放问题。注意:auto_ptr的复制会造成它本身的修改,原有的auto_ptr将不再指向任何对象,

而由新的auto_ptr接管对象内存,并负责自动删除。因此auto_ptr复制后不能再使用,且不能复制const

auto_ptr。

boost库中提供了一种新型的智能指针shared_ptr,它解决了多个指针间共享对象所有权的问题,同时

也满足容器对元素的要求,因而可以安全地放入容器中。shared_ptr解决了auto_ptr移动语义的破坏

性。

关于auto_ptr与shared_ptr使用请参考C++标准库的相关书籍。

示例:使用RAII不需要显式地释放互斥资源。

    class My_scope_lock
    {
    public:
       My_scope_lock(LockType& _lock):m_lock(_lock)
        {
           m_lock.occupy();
        }
       ~My_scope_lock()
        {
           m_lock.relase();
        }
    protected:
       LockType    m_lock;
    }
    bool class Data::Update()
    {
       My_scope_lock l_lock(m_mutex_lock);
       if()
        {
           return false;
        }
       else
        {
           //execute
        }
       return true;
    }

原创粉丝点击