Skip to content

内存溢出

内存溢出就是你要求分配的内存超出了系统能给你的,系统不能满足需求,于是产生溢出。一个盘子用尽各种方法只能装4 个果子,你装了5个,结果掉倒地上不能吃了。这就是溢出!比方说栈,栈满时再做进 栈必定产生空间溢出,叫上溢,栈空时再做退栈也产生空间溢出,称为**下溢**。就是分配的内存不足以放下数据项序列,称为**内存溢出**.

  • ??如果申请不到需要容量的空间,会有什么后果??我记得之前在阅读APUE的时候,里面有一个实例,在使用malloc去申请空间后,会判断申请到的空间和申请的空间大小是否相等,如果不相等,是会报错的。需要再看看这个实例。

内存泄漏

内存泄漏是指你向系统申请分配内存进行使用(new),可是使用完了以后却不归还(delete),结果你申请到的那块内存你自己也不能再访问(也许你把它的地址给弄丢了),而系统也不能再次将它分配给需要的程序。也就是说,这片内存空间是无法再被使用了。 以发生的方式来分类,内存泄漏可以分为4 类:

  1. 常发性内存泄漏。发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。
  2. 偶发性内存泄漏。发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。
  3. 一次性内存泄漏。发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块仅且一块内存发生泄漏。比如,在类的构造函数中分配内存,在析构函数中却没有释放该内存,所以内存泄漏只会发生一次。
  4. 隐式内存泄漏。程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。

从用户使用程序的角度来看,内存泄漏本身不会产生什么危害,作为一般的用户,根本感觉不到内存泄漏的存在。真正有危害的是内存泄漏的**堆积**,这会最终消耗尽系统所有的内存。从这个角度来说,一次性内存泄漏并没有什么危害,因为它不会堆积,而隐式内存泄漏危害性则非常大,因为较之于常发性和偶发性内存,泄漏它更难被检测到。

内存越界

何谓内存访问越界,简单的说,你向系统申请了一块内存,在使用这块内存的时候,超出了你申请的范围。 内存越界这样的错误引起的问题存在极大的不确定性,有时大,有时小,有时可能不会对程序的运行产生影响。这种错误,大部分情况会抛出AV(access violation)异常,有时不抛出异常, 有时导致软件直接崩溃(类似"Abnormal program termination");而且抛出异常的地方大多不是越界访问的地方(比如下面的例子),让人摸不着头脑; 什么原因会造成内存越界使用呢?有以下几种情况,可供参考: 例1:

        char buf[32] = {0};
        for(int i=0; i<n; i++)// n < 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

例4:

char *tmpc=new char[len-48+1];  
memset(tmpc,0,len); 
//我们申请了len-48+1个字节的内存空间,但是在memeset的时候却填充了len个字节的空间, 
//多填充了47个字节的空间,而这个越界填充可能就非法填充了tmpStr内部的内存空间,破坏了tmpStr内部的结构,
//从而造成std::string在函数operator+=的时候抛出异常!
tmpStr += funCode;//AV异常抛出的地方 

常用的内存操作函数都存在这种隐患,常用的内存操作函数如下:

sprintf snprintf vsprintf vsnprintf strcpy strncpy strcat memcpy memmove memset bcopy

当这样的代码一旦运行,错误就在所难免,会带来的后果也是不确定的,通常可能会造成如下后果:

  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就频繁发生,而你增加的代码绝不会有任何问题,这时你就应该考虑是否是某些内存被破坏了。

排查的原则,首先是保证能重现错误,根据错误估计可能的环节,逐步裁减代码,缩小排查空间。如果有用到自己编写的动态库的情况,要确保动态库的编译与程序编译的环境一致。

缓冲区溢出:

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

栈溢出就是缓冲区溢出的一种。

昨天我就碰到了数组越界导致的错误。让我比较好奇的是:

  1. c++没有数组越界检查机制
  2. 数组越界之后,居然能够访问到数据,虽然这些数据都是非法的。

这些是需要我进行研究的

关于数组越界,涉及到如下问题:

  1. 使用char array来保存字符串,要使用如下写法:
p_arr=new char[strlen(str)+1]

+1是为了要给'\0'占个座,防止越界。 刚刚按照这个网址http://blog.csdn.net/sxhelijian/article/details/8928528/ 上的事例用gdb调试了一下,并没有发现什么大的问题,有一个问题就是: p 变量 和cout输出的内容不相同,如下

(gdb) r
Starting program: /home/dk/linux_and_c/cpp/array_out_of_bound/cin_array_2 
abcde
abcde

Breakpoint 1, main () at cin_array_2.cpp:8
8       cout<<a<<endl;  
(gdb) p a
$3 = "abcd"
(gdb) p b
$4 = "abcd"
(gdb) c
Continuing.
abcde
abcde

Program exited normally.

显然这就是程序的异常所在,程序已经发生了越界。

什么是越界访问??

参见《内存溢出、内存泄露、内存越界、缓冲区溢出、栈溢出》

检查下标是否越界数组类

可以参考如下代码:

#include <iostream>
#include <string>
using namespace std;
class check
{
public:
    check(char*s)
    {
        str=new char[strlen(s)+1];
        strcpy(str,s);
        len=strlen(s);
    }
    char operator[](int n)
    {
        if(n>len-1)
        {
            cout<<"数组下标越界"<<endl;
            return ' ';
        }
        else
        {
            cout<<"数组下标没有越界"<<endl;
            return *(str+n);
        }
    }
    void Print(){cout<<str<<endl;}
private:
    char *str;
    int len;
};

void main()
{
    check array("GoodMorning");//类的构造函数
    array.Print();
    cout<<"Location 0:"<<array[0]<<endl;//判断下标为0是否越界
    cout<<"Location 20:"<<array[20]<<endl;//判断下标为20是否越界
}