C++语言编程规范-资源分配和释放
01
原则 6.1 明确产品动态内存的申请与释放原则
说明:之所以存在大量内存问题,主要原因是申请与释放内存的规则混乱:
02
规则 6.1 明确 operator new 的行为和检查策略
说明:当 operator new 无法满足某一内存分配需求时,默认会抛异常,也可以返回空指针(通过编译选项设
置)。团队明确 operator new 的操作。在申请内存后,要立即检查指针是否为 NULL 或进行异常处理,C++
示例:捕获异常来处理申请内存失败情况
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;
}
03
规则 6.2 释放内存后,要立即将指针设置为 NULL,防止产生野指针
说明:free 或 delete 释放内存后,立即将指针设置为 NULL,防止产生“野指针”。这种判断最好能够封装
起来,见建议 6.2。
示例:
char* pBuffer = new char[BUFFER_SIZE];
//…
delete [] pBuffer;
pBuffer = NULL;
04
规则 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 个未被销毁,因为它
们的析构函数根本没有被调用,并且结果是不可预测的,编译器不同,结果也不同。
05
规则 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);
06
规则 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 ;
由系统函数进行分配内存(如【1】所示),内存释放时分别由【2】释放数组单个元素和【3】释放数组本身内存一起完成的。 但是中间有个条件,每次只取 1000 个文件,如果目录下的文件大于 1000 就跳出,后面的就不会再处理 (【2】没有执行到)。当本地目录下文件数比较小,小于等于 1000 时没有内存泄漏;而当本地目录下的 文件大于 1000 时,就会导致内存泄漏。所以释放指针数组时,请注意首先释放其每个元素的内存空间。正确的做法是在【3】之前加上:
for(int j = i ; j < n; ++j)
{
free(namelist[i]);
}
07
规则 6.6 不要返回局部对象指针
说明:局部对象在定义点构造,在同一作用域结束时立即被销毁。
示例:
char* GetParameter ()
{
CDBConnect DBConnect;
//………………..
return DBConnect.GetString("ParamValue");
}
由于对象 DBConnect 已经析构,对应的指针已经被释放,从而后续访问非法内存,导致系统coredump。
08
规则 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);
}
09
建议 6.1 使用 new, delete 的封装方式来分配与释放内存
说明:推荐使用类似的如下宏,可以在一定程度上避免使用空指针,野指针的问题。
#define H7W_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了。
10
建议 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,因此不应使用 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;
}