Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1256792
  • 博文数量: 177
  • 博客积分: 1528
  • 博客等级: 上尉
  • 技术积分: 1891
  • 用 户 组: 普通用户
  • 注册时间: 2010-12-15 18:03
文章分类

全部博文(177)

文章存档

2020年(1)

2018年(19)

2017年(4)

2016年(21)

2015年(40)

2014年(13)

2013年(26)

2012年(16)

2011年(37)

我的朋友

分类: C/C++

2011-03-24 12:02:19

摘之:《高质量C++/C编程指南》

       发生内存错误是件非常麻烦的事情。编译器不能自动发现这些错误,通常是在程序运行时才能捕捉到。而这些错误大多没有明显的症状,时隐时现,增加了改错的难度。有时用户怒气冲冲地把你找来,程序却没有发生任何问题,你一走,错误又发作了。

常见的内存错误:

u       内存分配未成功,却使用了它。

编程新手常犯这种错误,因为他们没有意识到内存分配会不成功。常用解决办法是,在使用内存之前检查指针是否为NULL。如果指针p是函数的参数,那么在函数的入口处用assert(p!=NULL)进行检查。如果是用malloc或new来申请内存,应该用if(p==NULL) 或if(p!=NULL)进行防错处理。

u       内存分配虽然成功,但是尚未初始化就引用它。

犯这种错误主要有两个起因:一是没有初始化的观念;二是误以为内存的缺省初值全为零,导致引用初值错误(例如数组)。

内存的缺省初值究竟是什么并没有统一的标准,尽管有些时候为零值,我们宁可信其无不可信其有。所以无论用何种方式创建数组,都别忘了赋初值,即便是赋零值也不可省略,不要嫌麻烦。

u       内存分配成功并且已经初始化,但操作越过了内存的边界。

例如在使用数组时经常发生下标“多1”或者“少1”的操作。特别是在for循环语句中,循环次数很容易搞错,导致数组操作越界。

u       忘记了释放内存,造成内存泄露。

含有这种错误的函数每被调用一次就丢失一块内存。刚开始时系统的内存充足,你看不到错误。终有一次程序突然死掉,系统出现提示:内存耗尽。

动态内存的申请与释放必须配对,程序中mallocfree的使用次数一定要相同,否则肯定有错误(new/delete同理)。

u       释放了内存却继续使用它。

有三种情况:

1)程序中的对象调用关系过于复杂,实在难以搞清楚某个对象究竟是否已经释放了内存,此时应该重新设计数据结构,从根本上解决对象管理的混乱局面。

2)函数的return语句写错了,注意不要返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁。

3)使用freedelete释放了内存后,没有将指针设置为NULL。导致产生“野指针”。

其对策如下:

l         用malloc或new申请内存之后,应该立即检查指针值是否为NULL。防止使用指针值为NULL的内存。

l         不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。

l         避免数组或指针的下标越界,特别要当心发生“多1”或者“少1”操作。

l         动态内存的申请与释放必须配对,防止内存泄漏。

l         freedelete释放了内存之后,立即将指针设置为NULL,防止产生“野指针”。

 
=======================================================================
 
*** glibc detected *** free(): invalid pointer:
*** glibc detected *** malloc(): memory corruption:
*** glibc detected *** double free or corruption (out): 0x00000000005c18a0 ***
*** glibc detected *** corrupted double-linked list: 0x00000000005ab150 ***

你是否遇到过这样的情况,太沮丧了,程序总是无端coredump,gdb到core文件里面也看不出个所以然来,这对于一个大型的商业系统来说太令人恐怖了,事故随时可能发生。

遇到棘手的问题,慌张是没用的,解决不了任何问题。先坐下来,喝杯茶,舒缓一下神经。

内存问题始终是c++程序员需要去面对的问题,这也是c++语言的门槛较高的原因之一。通常我们会犯的内存问题大概有以下几种:

1.内存重复释放,出现double free时,通常是由于这种情况所致。
2.内存泄露,分配的内存忘了释放。
3.内存越界使用,使用了不该使用的内存。
4.使用了无效指针。
5.空指针,对一个空指针进行操作。


对于第一种和第二种,第五种情况,就不用多说,会产生什么后果大家应该都很清楚。

第四种情况,通常是指操作已释放的对象,如:
1.已释放对象,却再次操作该指针所指对象。
2.多线程中某一动态分配的对象同时被两个线程使用,一个线程释放了该对象,而另一线程继续对该对象进行操作。

我们重点探讨第三种情况,相对于另几种情况,这可以称得上是疑难杂症了(第四种情况也可以理解成内存越界使用)。

内存越界使用,这样的错误引起的问题存在极大的不确定性,有时大,有时小,有时可能不会对程序的运行产生影响,正是这种不易重现的错误,才是最致命的,一旦出错破坏性极大。

什么原因会造成内存越界使用呢?有以下几种情况,可供参考:
例1:
        char buf[32] = {0};
        for(int i=0; i 32
        {
            buf[i] = 'x';
        }
        ....
       
例2:
        char buf[32] = {0};
        string str = "this is a test sting !!!!";
        sprintf(buf, "this is a test buf!string:%s", str.c_str()); //out of buffer space
        ....
       
例3:
        string str = "this is a test string!!!!";
        char buf[16] = {0};
        strcpy(buf, str.c_str()); //out of buffer space
       
类似的还存在隐患的函数还有:
        strcat,vsprintf等

同样,memcpy, memset, memmove等一些内存操作函数在使用时也一定要注意。
       
当这样的代码一旦运行,错误就在所难免,会带来的后果也是不确定的,通常可能会造成如下后果:

1.破坏了堆中的内存分配信息数据,特别是动态分配的内存块的内存信息数据,因为操作系统在分配和释放内存块时需要访问该数据,一旦该数据被破坏,以下的几种情况都可能会出现。
        *** glibc detected *** free(): invalid pointer:
        *** glibc detected *** malloc(): memory corruption:
        *** glibc detected *** double free or corruption (out): 0x00000000005c18a0 ***
        *** glibc detected *** corrupted double-linked list: 0x00000000005ab150 ***       

2.破坏了程序自己的其他对象的内存空间,这种破坏会影响程序执行的不正确性,当然也会诱发coredump,如破坏了指针数据。

3.破坏了空闲内存块,很幸运,这样不会产生什么问题,但谁知道什么时候不幸会降临呢?

通常,代码错误被激发也是偶然的,也就是说之前你的程序一直正常,可能由于你为类增加了两个成员变量,或者改变了某一部分代码,coredump就频繁发生,而你增加的代码绝不会有任何问题,这时你就应该考虑是否是某些内存被破坏了。

排查的原则,首先是保证能重现错误,根据错误估计可能的环节,逐步裁减代码,缩小排查空间。
检查所有的内存操作函数,检查内存越界的可能。常用的内存操作函数:
sprintf snprintf
vsprintf vsnprintf
strcpy strncpy strcat
memcpy memmove memset bcopy

如果有用到自己编写的动态库的情况,要确保动态库的编译与程序编译的环境一致。

保持好的编码习惯是杜绝错误的最好方式!

阅读(2967) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~

GFree_Wind2011-03-24 12:15:14

用valgrind可以很快的定位内存问题。