c语言的内存泄漏问题
(2016-02-14 09:52:06)堆泄漏
“堆泄漏”即常说的内存泄漏,是嵌入式软件里的常见问题,会导致软件运行一段时间后内存耗尽。
什么是”堆泄漏”?
内存分配和释放的操作是程序员根据需要动态随机发起,程序本身(或编译工具)无法自动判断某块已分配的内存什么时候不再被使用,必须由程序员自己手动调用free释放,以便为其他程序腾出空间。而一旦程序员忘记释放某块内存,它就不能回到可用内存,系统总的可分配内存就随之减少,这就是内存泄漏。注意这里的内存特指堆(heap),只有堆内存才需要程序员自己控制分配和释放。所以内存泄漏和堆泄漏是同一概念。
新手对泄漏这个词往往感到不理解,不就是分配后忘记释放,怎么叫泄漏呢?叫内存丢失不是更通俗么?
关于这点,可以打个比方,分配内存就是从银行贷款,而释放内存就是给银行还钱。如果有人借了钱却赖帐不还,那么银行可支配的钱就会减少,银行总资产就被损失或泄漏。类似,堆是一块固定大小内存,“借”给不同程序使用,如果某个程序只借不还,堆管理所能支配的内存就减少,因此内存泄漏是针对系统中总的可支配内存资源来说,而并不是物理内存真的丢失。从这个角度理解,leak绝对比lost更准确生动:一种资源在封闭系统中循环使用,如果部分资源无法回到循环,不正是泄漏到封闭系统之外了么?
借钱不还的银行客户越来越多,最终银行就会因为没钱放贷周转而破产。同样发生内存泄漏,直接的表现就是软件运行越来越慢,最终甚至因分不到内存而崩溃。(所以说一定要判断malloc的返回值,不是每次都能从银行借到钱滴)
隐式泄漏
是指某内存已使用完,明明可以早点free掉,却非等到软件退出前才释放,俗称“占着XX不XX”,虽然程序最终释放了所有内存,严格意义上没有泄漏,但某些场合隐式泄露同样会导致严重后果:比如某长期运行的服务器程序,如果不断分配而不及时释放内存,最后系统很可能在运行中途就因堆内存耗尽而crash,因此内存使用过程中,不但要确保释放内存,而且用完要尽快释放,而不要全等到退出前释放,以消除隐式泄漏,确保内存占用峰值不超过系统堆资源上限。
我们先从一个函数来分析内存泄漏的原因:
char *doSomething()
{
char *p;
char *q;
if( ( p=malloc( 1024 ) ) ==NULL ) returnNULL;
if( ( q=malloc(2000 ) ) ==NULL ) return NULL;
.......
returnp;
}
分析一下这个函数,我们会发现开发人员考虑了p和q内存分配失败的问题,但是却考虑不周,加入,p内存分配成功,而q内存分配失败,那么returnNULL后,p指向的1024个单元的内存就丢失了,造成了内存泄漏。
造成内存泄漏的原因一般有以下几种原因:
1、开发人员动态申请内存后,忘记了释放内存
2、多个开发人员在合作开发的过程中,由于沟通不足,没有明确的规定由谁来申请内存,有谁来释放内存
3、free()函数是根据malloc()申请的内存控制信息来释放内存的,换句话说,free只能释放由malloc函数返回的内存指针,而在这期间,如果malloc分配的内存,其指针发生了变化,free函数将不能释放该内存,从而造成内存泄漏。关于这一点可以举个例子:
void doSomething( char *ptr )
{
char *p;
int i;
if( ptr == NULL ) return; // 入口参数合法性检查
if(( p= (char *) malloc ( 1024 ) ) ==NULL ) return; //分配空间并检查
for( i=0; i< 1024; i++ )
{
*p++ = *ptr++;
}
......
free( p );
return;
}
这个函数考虑了动态申请内存,以及释放内存,但是忽略了一点,就是在释放前p的值已经发生了改变,所以free(p)函数执行失败。因此仍然会造成内存泄漏。要应对这种问题,就是要保存p指针,例如可以采用如下方式:
* ( p+i) = *(ptr+i) ; 或者 *( p + i)= *ptr++;
这样就回避了以上问题。