c++中内存泄露和内存溢出

c++中的内存泄漏与内存溢出是比较常见的bug

1 内存溢出与内存泄露

你真的明白内存溢出与内存泄露的区别么?

1.1 内存泄漏

内存泄露是指程序在申请内存后,无法释放已申请的内存空间,这块空间一直被占着。
当然一次泄露可能不会出现问题,但程序多次累积泄露后的后果就是内存耗尽。
内存泄露累积的后果是内存溢出,但是内存溢出不一定是内存泄露造成的,还可能是堆栈达到最大值造成的溢出等。

1.1.1 内存泄露的分类

内存泄露的问题重启计算机可以解决,但也可能再次发生泄露。内存泄露与硬件无关,是软件设计上的bug
1.常发性内存泄露
发生内存泄露的代码会被多次执行,每次执行都会导致一块内存泄露
2.偶发性泄露
发生泄露的代码只在某些特定环境或操作下才发生泄露
3.一次性泄露
发生泄露的代码只会被执行一次,或总会有一块仅且一块内存发生泄露。
例如构造函数中分配的内存,析构函数没有释放
4.隐式内存泄露
程序运行不停的分配内存,但是直到结束的时候才释放所有的内存。
虽然程序最终会释放所有的内存,但是由于运行时间周期太长,不及时释放内存也会导致内存最终耗尽,所以这种也算隐式的内存泄露

1.1.2 内存泄露的几种情况

1.在类的构造函数和析构函数中没有匹配的调用new和delete函数
2.没有正确地清楚嵌套的对象指针
3.在释放对象数组的时候delete没有使用方括号
4.指向对象的指针数组不等同于对象数组
对象数组:数组中存放的是对象,释放的时候delete[]
指向对象的指针数组:数组中存放的是指向对象的指针,不仅要释放每个对象的空间,还要释放每个指针的空间,delete只是释放了每个指针,没有释放对象的空间;
正确的做法是通过一个循环,先将对象释放了,再将指向对象的指针释放
5.类中有指针变量,缺少拷贝构造函数
没有自定义拷贝构造函数的时候,针对指针变量的拷贝,是值拷贝,会使得两个指针指向同一块地址。
那么析构的时候可能会析构两次同一块地址。
所以类里面有指针变量的话,必须显示的写拷贝构造函数和重载赋值运算符;要么禁用拷贝构造函数与赋值运算符
6.缺少重载赋值运算符
与5类似,针对指针变量的拷贝问题,值拷贝带来的内存泄露
内存泄漏
7.没有将基类的析构函数定义为虚函数
当基类指针指向子类对象时,如果基类的析构函数不是virtual,那么子类的析构函数将不会被调用,子类的资源没有正确是释放,因此造成内存泄露

1.1.3 内存泄露怎么检测与预防

关于内存泄露的原因已经知道,针对性的修改和预防就可以了。这里主要看看怎么检测程序是否发生了内存泄露

  1. 首先利用ps命令
    根据ps命令 ps -aux 观测指定进程的vsz值是否增长
    或者命令 ps vx|grep 进程号 —–看size和rss段是否在增加
  2. 利用检测工具,推荐valgrind
    如果代码review能看出具体的内存泄露的地方话更好,不行的话我们可以依赖相应的检测工具。这里valgrind使用的较多就主要看这个
    关于valgrind的安装不介绍了,这里直接来个例子
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #include <stdio.h>
    #include <stdlib.h>
    char* getMemory()
    {
    char *p = (char *)malloc(30);
    return p;
    }

    int main()
    {
    char *p = getMemory();
    p = NULL;
    return 0;
    }

首先在编译的时候加上-g调试参数,然后调用命令

1
valgrind --tool=memcheck --leak-check=yes --show-reachable=yes ./a.out

结果
可以看到具体的内存泄露的某行代码,关于valgrind的用法以及其包含的一些工具命令后面详细介绍

1.2 内存溢出

程序申请内存时,没有足够的内存供申请者使用,造成内存不够用,此时一般会报错OOM,即所谓的内存溢出。

2. 野指针

指向已经被释放或访问受限内存的指针

2.1 野指针的原因

1.指针变量没有被初始化
2.指针被free或delete后,没有置为NULL
free和delete只是把指针所指向的内存给释放掉,并没有把指针本身干掉,此时指针指向的是“垃圾”内存。释放后的指针应该被置为NULL
3.指针操作超越了变量的作用范围
比如返回指向栈内存的指针就是野指针。

3.内存越界

是指向系统申请一块内存后,使用时却超出申请范围。内存溢出是申请内存的时候就不够
比如一些操作内存的函数:sprintf、strcpy、strcat、vsprintf、memcpy、memset、memmove。当造成内存泄漏的代码运行时,所带来的错误是无法避免的,通常会造成
1.破坏了堆中内存内存分配信息数据
2.破坏了程序其他对象的内存空间
3.破坏了空闲内存块

4.缓冲区溢出

程序为了临时存取数据的需要,一般会分配一些内存空间称为缓冲区。如果向缓冲区中写入缓冲区无法容纳的数据,机会造成缓冲区以外的存储单元被改写,称为缓冲区溢出。
而栈溢出是缓冲区溢出的一种,原理也是相同的。
分为上溢出和下溢出。其中,上溢出是指栈满而又向其增加新的数据,导致数据溢出;下溢出是指空栈而又进行删除操作等,导致空间溢出