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;
}
- C++ 6 资源分配和释放
- 深究跨dll的资源分配和释放问题
- 深究跨dll的资源分配和释放问题
- VC资源分配、释放表
- 资源的分配与释放
- VC资源分配、释放表
- VC资源分配、释放表
- VC资源分配、释放表
- VC资源分配、释放表
- VC资源分配、释放表
- VC资源分配、释放表
- (c#) 销毁资源和释放内存
- (c#) 销毁资源和释放内存
- 内存分配和释放
- 内存分配和释放
- 内存分配和释放
- 内存分配和释放
- c和c++中的内存分配和内存释放函数
- 解决tomcat启动时隐藏命令行
- C++编程规范 4 类
- C++编程规范 5 作用域、模板和C++其他特性
- ZZ 常用算法经典代码(C++版)
- 使用AES算法对文件进行加密/解密的操作(JAVA)
- C++ 6 资源分配和释放
- 一个是阆苑仙葩,一个是美玉无瑕
- C++ 编程规范 7 异常与错误处理
- C++编程规范 8 标准库
- C++编程规范 9 程序效率
- Linux内核数据包处理流程-数据包接收(2)
- WebApp与Native App有何区别
- android 图片平铺实现
- C++编程规范 10并发