新浪博客

C++:堆栈溢出一般是由什么原因导致的 ;解释:内存溢出、内存泄露、内存越界、缓冲区溢出

2015-11-07 16:23阅读:
堆栈溢出一般是由什么原因导致?
1.没有回收垃圾资源,存储空间垃圾太多内存已满。
2.层次太深的递归调用。重复调用自己导致内存不足。


解释:内存溢出、内存泄露、内存越界、缓冲区溢出
内存溢出就是你要求分配的内存超出了系统能给你的,系统不能满足
需求,于是产生溢出。
================================================================
内存泄漏是指你向系统申请分配内存进行使用(new),可是使用
完了以后却不归还(delete),结果你申请到的那块内存你自己也不能
再访问(也许你把它的地址给弄丢了),而系统也不能再次将它分配
给需要的程序。一个盘子用尽各种方法只能装4 个果子,你装了5
个,结果掉倒地上不能吃了。这就是溢出!比方说栈,栈满时再做进
栈必定产生空间溢出,叫上溢,栈空时再做退栈也产生空间溢出,称
为下溢。就是分配的内存不足以放下数据项序列,称为内存溢出.
以发生的方式来分类,内存泄漏可以分为4 类:
1. 常发性内存泄漏。发生内存泄漏的代码会被多次执行到,每次被
执行的时候都会导致一块内存泄漏。
2. 偶发性内存泄漏。发生内存泄漏的代码只有在某些特定环境或操
作过程下才会发生。常发性和偶发性是相对的。对于特定的环境,偶
发性的也许就变成了常
发性的。所以测试环境和测试方法对检测内存
泄漏至关重要。
3. 一次性内存泄漏。发生内存泄漏的代码只会被执行一次,或者由
于算法上的缺陷,导致总会有一块仅且一块内存发生泄漏。比如,在
类的构造函数中分配内存,在析构函数中却没有释放该内存,所以内
存泄漏只会发生一次。
4. 隐式内存泄漏。程序在运行过程中不停的分配内存,但是直到结
束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终
程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几
天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所
有内存。所以,我们称这类内存泄漏为隐式内存泄漏。
从用户使用程序的角度来看,内存泄漏本身不会产生什么危害,作为
一般的用户,根本感觉不到内存泄漏的存在。真正有危害的是内存泄
漏的堆积,这会最终消耗尽系统所有的内存。从这个角度来说,一次
性内存泄漏并没有什么危害,因为它不会堆积,而隐式内存泄漏危害
性则非常大,因为较之于常发性和偶发性内存,泄漏它更难被检测到


=================================================================
内存越界:
何谓内存访问越界,简单的说,你向系统申请了一块内存,在使用这块内存的时候,超出了你申请的范围。
内存越界使用,这样的错误引起的问题存在极大的不确定性,有时大,有时小,有时可能不会对程序的运行产生影响,正是这种不易重现的错误,才是最致命的,一旦出错破坏性极大。
什么原因会造成内存越界使用呢?有以下几种情况,可供参考:
例1:
char buf[32] = {0};
for(int i=0; i< 32 or n > 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
如果有用到自己编写的动态库的情况,要确保动态库的编译与程序编译的环境一致。

=================================================================
缓冲区溢出:
缓冲区溢出是指当计算机向缓冲区内填充数据位数时超过了缓冲区本身的容量溢出的数据覆盖在合法数据上,理想的情况是程序检查数据长度并不允许输入超过缓冲区长度的字符,但是绝大多数程序都会假设数据长度总是与所分配的储存空间相匹配,这就为缓冲区溢出埋下隐患.操作系统所使用的缓冲区又被称为'堆栈'. 在各个操作进程之间,指令会被临时储存在'堆栈'当中,'堆栈'也会出现缓冲区溢出。
栈溢出:
 栈溢出就是缓冲区溢出的一种。由于缓冲区溢出而使得有用的存储单元被改写,往往会引发不可预料的后果。程序在运行过程中,为了临时存取数据的需要,一般都要分配一些内存空间,通常称这些空间为缓冲区。如果向缓冲区中写入超过其本身长度的数据,以致于缓冲区无法容纳,就会造成缓冲区以外的存储单元被改写,这种现象就称为缓冲区溢出。
  栈溢出就是缓冲区溢出的一种。
-----------------------------------------------------------------------------------

浅谈C语言内存管理、内存泄露、堆栈

1.内存分配区间:
对于一个C语言程序而言,内存空间主要由五个部分组成:代码段(.text)、数据段(.data)、静态区(.BSS)、堆和栈组成。
BSS段:BSS段(bss segment)通常是指用来存放程序中未初始化的全局变量和静态变量 (这里注意一个问题:一般的书上都会说全局变量和静态变量是会自动初始化的,那么哪来的未初始化的变量呢?变量的初始化可以分为显示初始化和隐式初始化,全局变量和静态变量如果程序员自己不初始化的话的确也会被初始化,那就是不管什么类型都初始化为0,这种没有显示初始化的就是我们这里所说的未初始化。既然都是0那么就没必要把每个0都存储起来,从而节省磁盘空间,这是BSS的主要作用)的一块内存区域。BSS是英文Block Started by Symbol的简称。BSS段属于静态内存分配。 BSS节不包含任何数据,只是简单的维护开始和结束的地址,即总大小,以便内存区能在运行时分配并被有效地清零。BSS节在应用程序的二进制映象文件中并不存在,即不占用磁盘空间 而只在运行的时候占用内存空间 ,所以如果全局变量和静态变量未初始化那么其可执行文件要小很多。

数据段:数据段(data segment)通常是指用来存放程序中已初始化的全局变量和静态变量的一块内存区域。数据段属于静态内存分配,可以分为只读数据段和读写数据段。 字符串常量等,但一般都是放在只读数据段中

代码段:代码段(code segment/text segment)通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读, 某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等,但一般都是放在只读数据段中

栈区:由系统自动分配,栈区的分配运算内置于处理器的指令集,当函数执行结束时由系统自动释放。存放局部变量。栈的缺点是:容量有限,当相应的区间被释放时,局部变量不可再使用。查询栈容量的命令:ulimits -s。栈是一块连续的区域,向高地址扩展,栈顶和容量是事先约定好的。

堆区:在程序的执行过程中才能分配,由程序员决定,编译器在编译时无法为他们分配空间,只有在程序运行时分配,所以被称为动态分配。堆是不连续的区域,向高地址扩展。由于系统用链表来描述空闲的地址空间,链表的遍历是由地地址向高地址的,故堆区是不连续的动态的存储空间。 1 int a = 0; //a在全局已初始化数据区 2 char *p1; //p1在BSS区(未初始化全局变量) 3 main() 4 { 5 int b; //b在栈区 6 char s[] = 'abc'; //s为数组变量,存储在栈区, 7 //'abc'为字符串常量,存储在已初始化数据区 8 char *p1,p2; //p1、p2在栈区 9 char *p3 = '123456'; //123456\0在已初始化数据区,p3在栈区 10 static int c =0//C为全局(静态)数据,存在于已初始化数据区 11 //另外,静态数据会自动初始化 12 p1 = (char *)malloc(10);//分配得来的10个字节的区域在堆区 13 p2 = (char *)malloc(20);//分配得来的20个字节的区域在堆区 14 free(p1); 15 free(p2); 16 }
2.内存出错误的原因:
(1)内存申请未成功,然后进行使用;//在编程时经常用if (p == NULL) 进行判断;
(2)内存申请成功,但是没有初始化,会造成内存出错;
(3)内存初始化成功,但是操作越界,比如在数组的操作当中加一;char a [5] = 'hello';会造成段错误,没有考虑到‘\0‘的存储空间,若越界访问的内存空间是空闲的,则程序可能不受影响。若空间已经被占用,若执行了非法操作,程序可能奔溃。
(4)忘记释放内存或者释放一部分则会造成内存泄露。

3.malloc 的使用:
(1)在申请时必须指明大小;
(2)判断是否申请成功,若不成功则不能进行使用,否则会造成内存出错;
(3)返回指针是一个void * ,所以在使用前必须进行强制转换;
(4)显式初始化,堆区的内容在自动分配时不会初始化(包括清零操作),所以程序中要进行必要的初始化。

4.free函数的使用:
(1)在申请完内存时,忘记释放或者释放一部分,会导致内存泄露;
(2)重复释放会导致内存出错;//当第一次释放内存时,指针指向的堆区会释放。此时,操作系统有可能给释放的堆区分配其他的应用程序,当进行第二次释放时,会破坏其他的应用程序的数据。
(3)在内存释放结束之后,指针要清空(p == NULL), 因为在执行free函数之后,指针指向的空间会释放,但是p仍然是一个地址值。
(4)malloc 必须和 free成对使用;
(5)free 只能释放堆区(动态存储区),不能释放静态区,还有栈区。

5.内存泄露:
当动态分配的内存不在使用时,它应给被释放,这样以后可以重新使用内存。分配内存但是在使用完毕之后不进行释放将会引起内存泄露。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。
在一个进程中创建多个线程如果对线程资源不进行释放phread_join(),则会造成内存泄露。

内存泄露和内存使用的区别:内存泄露是内存已经被占用,但是不可以重新分配使用。

6.堆和栈的区别
(1)申请方式
(2)操作系统的相应
(3)申请的大小限制
(4)申请速度
(5)堆和栈的存储内容
堆区的头部用一个字节存放堆区的大小,其他的内容由程序员自己安排;
栈区:在函数调用子函数的时候,首先进栈的是函数调用语句的下一条可执行语句的地址,然后是函数的各个参数进栈,在大多数C编译器中,函数的参数是从右向左一次进栈,接下类是局部变量进栈。当本次函数执行结束时候,首先出栈的是局部变量,其次是函数参数,最后是栈顶指向的可执行语句的地址。


解释:内存溢出、内存泄露、内存越界、缓冲区溢出、栈溢出
浅谈C语言内存管理、内存泄露、堆栈

我的更多文章

下载客户端阅读体验更佳

APP专享