软件诊断学-存泄漏与内存管理问题

内存泄漏是指程序中已动态分配的堆内存由于某种原因未释放或者无法释放,造成的内存浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。内存泄漏是C/C++语言中最常见的内存管理问题,因为C/C++语言需要开发者手动申请和释放内存,如果内存使用不当,就容易造成内存泄漏。本节将介绍内存泄漏的现象,定位思路,常用工具,案例分析和避免方法。

4.1.1 内存泄漏的现象

内存泄漏的主要表现是程序占用的内存持续增长,而不会随着程序的运行而释放。这会导致程序的性能下降,响应变慢,甚至出现内存不足的错误。内存泄漏的现象可以通过以下几种方式观察:

  • 使用操作系统的任务管理器或者进程监视器,查看程序的内存使用情况,如果发现程序的内存占用一直上升,而不会下降,那么很可能存在内存泄漏。
  • 使用程序自带的内存监控功能,如果有的话,查看程序的内存分配和释放的情况,如果发现程序分配的内存远远大于释放的内存,那么很可能存在内存泄漏。
  • 使用专门的内存检测工具,如Valgrind,Purify,Dr.Memory等,对程序进行内存泄漏的检测,如果发现程序有未释放的内存块,那么很可能存在内存泄漏。

4.1.2 内存泄漏的定位思路

内存泄漏的定位思路是找出程序中哪些地方分配了内存,但是没有释放,或者释放不正确。这需要对程序的内存管理逻辑有清晰的了解,以及对内存检测工具的熟练使用。一般来说,可以按照以下步骤进行内存泄漏的定位:

  • 使用内存检测工具对程序进行内存泄漏的检测,获取内存泄漏的报告,包括内存泄漏的数量,大小,位置,类型等信息。
  • 分析内存泄漏的报告,找出内存泄漏的原因,如是否是由于忘记释放内存,或者释放了错误的内存,或者释放了多次的内存,或者释放了不属于自己的内存等。
  • 根据内存泄漏的原因,修改程序的代码,修复内存泄漏的问题,如添加或者删除相应的内存释放语句,或者使用智能指针等技术来管理内存的生命周期等。
  • 重新使用内存检测工具对程序进行内存泄漏的检测,验证内存泄漏是否已经解决,如果没有,重复上述步骤,直到内存泄漏完全消除。

4.1.3 内存泄漏的常用工具

内存泄漏的常用工具有以下几种:

  • Valgrind:Valgrind是一个Linux下的开源的内存检测工具,它可以检测程序的内存泄漏,内存错误,内存访问越界,内存使用未初始化等问题。Valgrind的使用方法是在程序运行时加上valgrind的命令行参数,如valgrind --leak-check=full ./program,然后在程序结束后,查看valgrind的输出报告,找出内存泄漏的地方。
  • Purify:Purify是一个商业的内存检测工具,它可以检测程序的内存泄漏,内存错误,内存访问越界,内存使用未初始化等问题。Purify的使用方法是在程序编译时加上purify的命令行参数,如purify gcc -o program program.c,然后在程序运行时,查看purify的图形界面,找出内存泄漏的地方。
  • Dr.Memory:Dr.Memory是一个Windows下的开源的内存,检测工具,它可以检测程序的内存泄漏,内存错误,内存访问越界,内存使用未初始化等问题。Dr.Memory的使用方法是在程序运行时加上drmemory的命令行参数,如drmemory -batch -- ./program,然后在程序结束后,查看drmemory的输出报告,找出内存泄漏的地方。

4.1.4 内存泄漏的案例分析

下面给出一个内存泄漏的案例,以及如何使用Valgrind工具进行定位和解决的过程。假设有以下的C++程序,它的功能是创建一个链表,然后删除链表中的奇数节点,最后打印链表的内容:

#include <iostream>
using namespace std;

struct Node {
    int data;
    Node* next;
    Node(int d): data(d), next(nullptr) {}
};

Node* createList(int n) {
    Node* head = new Node(1);
    Node* cur = head;
    for (int i = 2; i <= n; i++) {
        cur->next = new Node(i);
        cur = cur->next;
    }
    return head;
}

void deleteOdd(Node* head) {
    Node* cur = head;
    Node* prev = nullptr;
    while (cur) {
        if (cur->data % 2 == 1) {
            if (prev) {
                prev->next = cur->next;
            }
            delete cur;
        } else {
            prev = cur;
        }
        cur = cur->next;
    }
}

void printList(Node* head) {
    Node* cur = head;
    while (cur) {
        cout << cur->data << &quot; &quot;;
        cur = cur->next;
    }
    cout << endl;
}

int main() {
    Node* head = createList(10);

该程序的预期输出是:

2 4 6 8 10

然而,该程序存在内存泄漏的问题,因为在删除奇数节点时,没有更新头节点的指针,导致头节点被释放后,无法访问后续的节点,造成内存的浪费。为了发现和解决这个问题,我们使用Valgrind工具对程序进行内存泄漏的检测,具体步骤如下:

  • 首先,我们将程序保存为leak.cpp文件,并使用g++编译器编译为可执行文件leak,命令如下:
g++ -o leak leak.cpp
  • 然后,我们使用Valgrind工具对可执行文件leak进行内存泄漏的检测,命令如下:
valgrind --leak-check=full ./leak
  • 接着,我们查看Valgrind的输出报告,发现以下的信息:
==1234== Memcheck, a memory error detector
==1234== HEAP SUMMARY:
==1234==     in use at exit: 40 bytes in 4 blocks
==1234==   total heap usage: 11 allocs, 7 frees, 88 bytes allocated
==1234==
==1234== 40 bytes in 4 blocks are definitely lost in loss record 1 of 1
==1234==    at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==1234==    by 0x4EBF9DF: operator new(unsigned long) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25)
==1234==    by 0x108A9E: createList(int) (leak.cpp:11)
==1234==    by 0x108B5C: main (leak.cpp:38)
==1234==
==1234== LEAK SUMMARY:
==1234==    definitely lost: 40 bytes in 4 blocks
==1234==    indirectly lost: 0 bytes in 0 blocks
==1234==      possibly lost: 0 bytes in 0 blocks
==1234==    still reachable: 0 bytes in 0 blocks
==1234==         suppressed: 0 bytes in 0 blocks
==1234==
==1234== For counts of detected and suppressed errors, rerun with: -v
==1234== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
  • 通过分析报告,我们发现程序在退出时,有40字节的内存在4个块中没有被释放,这些内存是在createList函数中使用new操作符申请的,而且这些内存是definitely lost,也就是说程序已经完全失去了对这些内存的控制,无法再访问或者释放。这就是内存泄漏的地方。
  • 根据报告,我们修改程序的代码,修复内存泄漏的问题,具体修改如下:
void deleteOdd(Node* head) {
    Node* cur = head;
    Node* prev = nullptr;
    while (cur) {
        if (cur->data % 2 == 1) {
            if (prev) {
                prev->next = cur->next;
            } else {
                //如果删除的是头节点,需要更新头节点的指针
                head = cur->next;
            }
            delete cur;
        } else {
            prev = cur;
        }
        cur = cur->next;
    }
}
  • 修改后,我们重新编译和运行程序,并使用Valgrind工具进行内存泄漏的检测,发现以下的信息:
==1234== Memcheck, a memory error detector
==1234== HEAP SUMMARY:
==1234==     in use at exit: 0 bytes in 0 blocks
==1234==   total heap usage: 11 allocs, 11 frees, 88 bytes allocated
==1234==
==1234== All heap blocks were freed -- no leaks are possible
==1234==
==1234== For counts of detected and suppressed errors, rerun with: -v
==1234== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
  • 通过分析报告,我们发现程序在退出时,没有任何内存泄漏的问题,所有申请的内存都被正确地释放了,说明内存泄漏已经解决。

4.1.5 内存泄漏的避免方法

内存泄漏的避免方法有以下几种:

  • 使用智能指针,如unique_ptr,shared_ptr,weak_ptr等,代替原始指针,自动管理内存的生命周期,避免手动操作内存,减少内存泄漏的风险。
  • 使用标准库的容器,如vector,list,map等,代替原始的数组,链表,哈希表等,自动管理内存的分配和释放,避免内存的浪费和错误。
  • 使用异常安全的代码,确保在异常发生时,能够正确地释放内存,或者使用try-catch-finally等语句,处理异常情况,避免内存的泄漏。
  • 使用内存检测工具,对程序进行内存泄漏的检测,及时发现并修复内存泄漏的问题,提高程序的质量和稳定性。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,463评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,868评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,213评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,666评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,759评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,725评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,716评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,484评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,928评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,233评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,393评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,073评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,718评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,308评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,538评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,338评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,260评论 2 352

推荐阅读更多精彩内容