内存泄漏

内存问题的可能情况

内存泄漏(memory leak)指由于疏忽或错误造成程序未能释放已经不再使用的内存。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。这样一直的内存泄漏下去,最后会导致机器的内存不足。在最糟糕的情况下,过多的可用内存被分配掉导致全部或部分设备停止正常工作,或者应用程序崩溃。

内存溢出(out of memory)是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。不同的编程语言,对内存溢出的处理也不一样。在某些情况下,可能直接导致程序异常或者崩溃。在另外一些情况下,可能是程序无法正常运行,或者性能下降。

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

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

内存泄漏分类

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

用户感知

从有计算机开始,就有内存的的存在,使用好内存对成为一个优秀的软件工程师至关重要。

但是从用户使用程序的角度来看,内存泄漏本身不会产生什么危害,作为一般的用户,根本感觉不到内存泄漏的存在。我们平时使用的PC或者MAC,或者应用程序即使存在内存泄漏,基本也不会有感知,普通用户根本不会长时间去使用一个应用程序。但是长时间的使用操作系统是非常有可能的,在以前的Windows系统,经常性的蓝屏,问题多数就跟内存的管理使用有关,尤其是内存泄漏的问题多。

真正有危害的是内存泄漏的堆积,这会最终消耗尽系统所有的内存。从这个角度来说,一次性内存泄漏并没有什么危害,因为它不会堆积,而隐式内存泄漏危害性则非常大,因为较之于常发性和偶发性内存泄漏它更难被检测到。

解决方案与预防

最简单的内存泄漏

如下的代码:

#include <stdio.h>
#include <stdlib.h>

void foo() {
    char *p = (char*) malloc(10);
    p[0] = 'a';
    printf("%s \n", p);
}

int main(int argc, char** argv) {
    foo();
    return 0;
}

这段代码看着没什么问题,申请的内存没有free掉,但是程序退出的时候会自动回收。确实是这样子,但是如果调用foo()函数的是线程,每一个线程都这样只申请内存不释放内存,那么每开一次新的线程就会申请一次内存,最后内存就会不断的攀升,最后吃完机器的内存。这是非常的明显的例子,这样的错误一般是不应该发生的,只需要由如下的编码习惯:

  • malloc和free函数配对使用,一个malloc对应一个free
  • new和delete或delete[]配对使用

那么申请的内存就会释放,可以避免掉这种简单错误。

C语言函数库一些不安全的函数

如下代码:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char** argv) {
    char* dst = "hello";
    const char* src = "Hi world";
    char* p = strcpy(dst, src);
    printf("%s \n", p);
    printf("%s \n", dst);
    return 0;
}

这段代码显然是有问题的,编译结果后,运行直接出错,不会有任何结果。

C和C++不能够自动地做边界检查,边界检查的代价是效率,于是常常就出现了越界的行为。一般来讲,C 在大多数情况下注重效率。然而,获得效率的代价是,C 程序员必须十分警觉以避免缓冲区溢出问题。而且这种行为往往是C语言未定义(Undefined Behavior)的行为,具体怎么处理,还得看编译器的脸色。

C语言标准库中的许多字符串处理和IO流读取函数是导致缓冲区溢出的罪魁祸首。我们有必要了解这些函数,在编程中多加小心。如下列出一些不安全的函数:

strcpy()
strcat()
sprintf()、vsprintf() 
gets()
getchar()、fgetc()、getc()、read()
scanf()、sscanf()、fscanf()、vfscanf()、vscanf()、vsscanf()
getenv()

以上的这些函数现在基本都有安全替代的版本,建议大家都使用安全替代版本。

内存泄漏检测工具

在一般的小型项目到还好,基本的问题都能在写代码的时候察觉。但是在大规模项目上,一个人要管理编写的代码可能上万行,还要与同事分工合作,不太可能一行一行的代码检查。所以机智的前人们早就开发了一些好用的内存检测工具,虽然这些内存检测工具不能把所有的问题都检测出来,但是常见的问题都是没问题。

valgrind内存检测工具集

Valgrind通常用来成分析程序性能及程序中的内存泄露错误,他是一套工具集。

1、memcheck:检查程序中的内存问题,如泄漏、越界、非法指针等。
2、callgrind:检测程序代码的运行时间和调用过程,以及分析程序性能。
3、cachegrind:分析CPU的cache命中率、丢失率,用于进行代码优化。
4、helgrind:用于检查多线程程序的竞态条件。
5、massif:堆栈分析器,指示程序中使用了多少堆内存等信息。
6、lackey:
7、nulgrind:

这几个工具的使用是通过命令:valgrind --tool=name 程序名来分别调用的,当不指定tool参数时默认是 --tool=memcheck。更多的详细使用请参考手册

对以上会出错的代码做检测:
valgrind --leak-check=full --show-reachable=yes --trace-children=yes ./main
结果如下:

==31754== Memcheck, a memory error detector
==31754== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==31754== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==31754== Command: ./main
==31754==
==31754==
==31754== Process terminating with default action of signal 11 (SIGSEGV)
==31754==  Bad permissions for mapped region at address 0x108784
==31754==    at 0x4C32E00: strcpy (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==31754==    by 0x1086C1: main (in /home/xingyaoma/main)
==31754==
==31754== HEAP SUMMARY:
==31754==     in use at exit: 0 bytes in 0 blocks
==31754==   total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==31754==
==31754== All heap blocks were freed -- no leaks are possible
==31754==
==31754== For counts of detected and suppressed errors, rerun with: -v
==31754== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Segmentation fault (core dumped)

结果会有许多的显示,按照结果的提示,就可以在代码中去找一些问题了。

在前面的示例中并没有把所有可能内存的问题都列出来,只是其中的一些情况。在实际开发中,配合valgrind工具,我们可以开发出稳定高效可靠的C/C++代码。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容

  • 由于 C 和 C++ 程序中完全由程序员自主申请和释放内存,稍不注意,就会在系统中导入内存错误。同时,内存错误往往...
    帅气滴点C阅读 1,041评论 0 2
  • “该死系统存在内存泄漏问题”,项目中由于各方面因素,总是有人抱怨存在内存泄漏,系统长时间运行之后,可用内存越来越少...
    7ee72f98ad17阅读 312评论 0 1
  • 1.内存泄漏 内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成...
    Sun灬2019阅读 245评论 0 1
  • 内存管理的目的就是让我们在开发中怎么有效的避免我们的应用出现内存泄漏的问题。内存泄漏大家都不陌生了,简单粗俗的讲,...
    宇宙只有巴掌大阅读 2,357评论 0 12
  • 行者顺达 岁岁重阳今重阳, 秋月晨曦晴空朗。 舞剑佩带随风旋, 翁练健步影流畅。 挥剑向天起舞茫, 斩断苍穹我心狂...
    行者顺达阅读 232评论 0 0