分类: C/C++
2009-05-26 23:39:46
前一篇日记已经讲了程序在内存的状态以及一些结构,那么在程序运行时使用内存肯定会碰到一些问题。最常见的就是段错误和内存泄漏,下面就简单的介绍一下这两种错误出现的原因、现象以及解决方法
一、内存泄漏
1) 什么是内存泄漏:简单来说所谓的内存泄漏,就是未释放不再使用的内存;
如:
void GetMemory( char *p )
{
p = (char *) malloc( 100 );
}
void Test( void )
{
char *str = NULL;
GetMemory( str );
strcpy( str, "hello world" );
printf( str );
}
这里申请了100字节大小的空间,但没有释放,就会导致内存泄漏
2) 会导致什么样的后果:
会使进程的速度减慢,因为进程因为申请的内存得不到释放,进程的体积就会越来越大。而体积大的进程更有可能被系统换出,让别的进程运行,而且大的进程在换进换出时花费的时间也更多(注意,申请的空间大小大都比较你实际申请的数字要大,因为malloc()所分配的内存通常会是2的整数次方,如申请212B实际会是256B),所以在不同的操作系统里可能对进程的大小有一个限值。32位的系统一般为4GB.
3) 内存泄漏的发生方式
1.常发性内存泄漏。发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。
2.偶发性内存泄漏。发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。
3.一次性内存泄漏。发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块且仅有一块内存发生泄漏。
4.隐式内存泄漏。程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。
4) 如何检测内存泄漏
1.检测内存泄漏的关键是要能截获住对分配内存和释放内存的函数的调用。截获住这两个函数,我们就能跟踪每一块内存的生命周期,比如,每当成功的分配一块内存后,就把它的指针加入一个全局的list中;每当释放一块内存,再把它的指针从list中删除。这样,当程序结束的时候,list中剩余的指针就是指向那些没有被释放的内存。这里只是简单的描述了检测内存泄漏的基本原理,详细的算法可以参见Steve Maguire的<
2.如果要检测堆内存的泄漏,那么需要截获住malloc/realloc/free和new/delete就可以了(其实new/delete最终也是用malloc/free的,所以只要截获前面一组即可)。对于其他的泄漏,可以采用类似的方法,截获住相应的分配和释放函数。比如,要检测BSTR的泄漏,就需要截获SysAllocString/SysFreeString;要检测HMENU的泄漏,就需要截获CreateMenu/ DestroyMenu。(有的资源的分配函数有多个,释放函数只有一个,比如,SysAllocStringLen也可以用来分配BSTR,这时就需要截获多个分配函数)
3.在Windows平台下,检测内存泄漏的工具常用的一般有三种,MS C-Runtime Library内建的检测功能;外挂式的检测工具,诸如,Purify,BoundsChecker等;利用Windows NT自带的Performance Monitor。这三种工具各有优缺点,MS C-Runtime Library虽然功能上较之外挂式的工具要弱,但是它是免费的;Performance Monitor虽然无法标示出发生问题的代码,但是它能检测出隐式的内存泄漏的存在,这是其他两类工具无能为力的地方。
这些工具的具体使用方法这里就不详细介绍了。有兴趣的可以去去网上搜集资料
二、段错误
1)段错误是什么
对于一个C程序员的新手来说,会经常碰到段误,那么段错误究竟是怎么回事呢?段错误是由于内存管理单元的异常所致。而此异常通常是由于解除一个未初始化或非法值的指针引起的。如果指针引用一个并不位于你的地址空间的地址。操作系统便会对此进行干涉。如:
int *p ;
*p= 0;//未初始化,段错误
2)通常导致段错误的几个直接原因:以及如何避免
1. 坏指针错误:在指针赋值之前就用它来引用内存,或者向库函数传送一个坏指针。另外还有可能是对指针进行释放之的再访问它的内容(可以在free后将它置为空值)
free(p);p = NULL;
struct mystruct *test;
test = myfucntion();//此函数可能返回为NULL;
if(test.a == 1) //如果test为 NULL时就会出现错误,正确的做法是在使用前判断一下:
if(test != NULL)
2. 改写错误。越过数组边界写入数据,在动态分配的内存两端之后写入数据,或者改写一些堆管理数据结构(如在动态分配内存之前的区域写入数据就很容易发生)
P = malloc(256);p[-1] =0;p[256] =0;
3. 指针释放引起的错误:释放同一个内存块两次。或释放一块未曾使用malloc分配的内存。或释放仍在使用的内存。或者释放一个无效的指针。
char *p;
…//并没有给P赋值
free(p);//错误
或者:
char *p;p=malloc(256);
….
free(p)
….
free(p)//前面释放之后再释放就会错误