内存泄漏是动态分配的内存块,在程序的数据空间中任何位置都没有指向它的指针。这类块是孤立内存。由于没有指向这些块的指针,程序无法引用它们,更谈不上释放它们。运行时检查会查找并报告这类块。
内存泄漏会导致占用的虚拟内存增加,且通常会导致产生内存碎片。这可能会降低程序及整个系统的性能。
通常情况下,导致出现内存泄漏的原因是未释放分配的内存,而又丢失了指向分配块的指针。下面是一些内存泄漏示例:
void foo() { char *s; s = (char *) malloc(32); strcpy(s, "hello world"); return; /* no free of s. Once foo returns, there is no */ /* pointer pointing to the malloc’ed block, */ /* so that block is leaked. */ } |
API 使用不当会导致泄漏。
void printcwd() { printf("cwd = %s\n", getcwd(NULL, MAXPATHLEN)); return; /* libc function getcwd() returns a pointer to */ /* malloc’ed area when the first argument is NULL, */ /* program should remember to free this. In this */ /* case the block is not freed and results in leak.*/ } |
总是在不再需要内存时便将其释放,并密切注意返回已分配内存的库函数,便可避免内存泄漏。如果使用这类函数,记得要适当地释放内存。
有时,内存泄漏一词用于指未释放的内存块。内存泄漏的这一定义很少用到,因为常见的编程惯例是,如果程序不久便会终止,就不释放内存。如果程序仍然有一个或多个指向内存块的指针,运行时检查不会将内存块按泄漏来报告。
mel(请参见内存泄漏 (mel) 错误)
air(请参见地址位于寄存器内 (air) 错误)
aib(请参见地址位于块内 (aib) 错误)
运行时检查只查找 malloc 内存的泄漏。如果程序未使用 malloc,运行时检查便无法找到内存泄漏。
在两种情况下,运行时检查会报告“可能的”泄漏。第一种情况是没有指针指向块开始处,但有指针指向块内部时。这种情况按“地址位于块内 (aib)”错误来报告。如果指针是指向块内部的迷失指针,便是真正的内存泄漏。但是,某些程序会根据需要有意反复移动指向数组的唯一指针来访问其条目。这种情况便不是内存泄漏。由于运行时检查无法区分这两种情况,因此会将这两种情况都按可能的泄漏来报告,由您来确定哪一种情况是真正的内存泄漏。
数据空间中没有指向内存块的指针,但寄存器中有指针时,便会出现第二种类型的可能泄漏。这种情况按“地址位于寄存器内 (air)”错误来报告。如果寄存器意外指向内存块或寄存器是后来丢失了的内存指针的旧副本,便是真正的泄漏。不过,编译器可以优化引用以及将指向内存块的唯一指针放入寄存器中,而不必将指针写入内存。这类情况便不是真正的泄漏。因此,如果程序经过优化,且报告是执行 showleaks 命令的结果,很可能不是真正的泄漏。所有其他情况便可能是真正的泄漏。有关更多信息,请参见showleaks 命令。
运行时泄漏检查要求使用标准 libc malloc/free/realloc 函数或基于这些函数的分配器。有关其他分配器,请参见运行时检查应用编程接口。.
如果启用了内存泄漏检查,则会在所测试的程序即将退出之前,自动执行内存泄漏扫描。检测到的所有泄漏都会报告出来。不应使用 kill 命令中止程序。下面是一个典型的内存泄漏错误消息:
Memory leak (mel): Found leaked block of size 6 at address 0x21718 At time of allocation, the call stack was: [1] foo() at line 63 in test.c [2] main() at line 47 in test.c |
UNIX 程序有一个 main 过程(在 f77 中称为 MAIN),它是程序的顶级用户函数。程序通常以两种方式终止:一种是调用 exit(3),另一种是从 main 返回。在后一种情况下,main 的所有局部变量都会在返回后超出作用域,而它们指向的所有堆块都会按泄漏来报告(除非有全局变量也指向这些块)。
常见的编程惯例是不释放分配给 main 中的局部变量的堆块,因为程序即将终止并从 main 返回,而不必调用 exit()。要防止运行时检查将这种块按内存泄漏来报告,请在 main 中最后一个可执行源代码行设置一个断点,以在 main 返回前停止程序。当程序在该处停止时,使用 showleaks 命令报告所有真正的泄漏,而忽略仅由 main 中的变量超出作用域导致的泄漏。
有关更多信息,请参见showleaks 命令。
在启用了泄漏检查时,程序退出时会自动生成泄漏报告。所有可能的泄漏都会报告出来,但前提是程序不是使用 kill 命令中止的。报告中的详细程度由 dbx 环境变量 rtc_mel_at_exit 控制(请参见设置 dbx 环境变量)。缺省情况下,会生成简短的泄漏报告。
报告按泄漏的合并大小排序。先报告真正的内存泄漏,然后报告可能的泄漏。详细报告包含详细的栈跟踪信息,其中包括行号和可用的源文件。
两种报告都包括内存泄漏错误的下列信息:
信息 |
说明 |
---|---|
大小 |
泄漏块的大小。 |
位置 |
泄漏块被分配到的位置。 |
地址 |
泄漏块的地址。 |
栈 |
分配时调用栈,由 check -frames 设限。 |
以下是相应的简短内存泄漏报告。
Actual leaks report (actual leaks: 3 total size: 2427 bytes) Total Num of Leaked Allocation call stack Size Blocks Block Address ========== ====== ========== ======================================= 1852 2 - true_leak < true_leak 575 1 0x22150 true_leak < main Possible leaks report (possible leaks: 1 total size: 8 bytes) Total Num of Leaked Allocation call stack Size Blocks Block Address ========== ====== ========== ======================================= 8 1 0x219b0 in_block < main |
以下是一个典型的详细泄漏报告。
Actual leaks report (actual leaks: 3 total size: 2427 bytes) Memory Leak (mel): Found 2 leaked blocks with total size 1852 bytes At time of each allocation, the call stack was: [1] true_leak() at line 220 in "leaks.c" [2] true_leak() at line 224 in "leaks.c" Memory Leak (mel): Found leaked block of size 575 bytes at address 0x22150 At time of allocation, the call stack was: [1] true_leak() at line 220 in "leaks.c" [2] main() at line 87 in "leaks.c" Possible leaks report (possible leaks: 1 total size: 8 bytes) Possible memory leak -- address in block (aib): Found leaked block of size 8 bytes at address 0x219b0 At time of allocation, the call stack was: [1] in_block() at line 177 in "leaks.c" [2] main() at line 100 in "leaks.c" |
可以随时通过使用 showleaks 命令获取泄漏报告,其中会报告自上次执行 showleaks 命令以来的新内存泄漏。有关更多信息,请参见showleaks 命令。
由于单个泄漏的数量可能会非常大,因此运行时检查会自动将同一位置分配的泄漏合并到一个合并泄漏报告中。是合并泄漏还是分别报告泄漏由 number-of-frames-to-match 参数控制,该参数通过 check -leaks 中的 -match m 选项或 showleaks 命令的 -m 选项指定。如果两个或更多泄漏分配时的调用栈与 m 帧在严格程序计数器等级匹配,便会在一个合并泄漏报告中报告这些泄漏。
假设有下列三个调用序列:
块 1 |
块 2 |
块 3 |
---|---|---|
[1] malloc |
[1] malloc |
[1] malloc |
[2] d() at 0x20000 |
[2] d() at 0x20000 |
[2] d() at 0x20000 |
[3] c() at 0x30000 |
[3] c() at 0x30000 |
[3] c() at 0x31000 |
[4] b() at 0x40000 |
[4] b() at 0x41000 |
[4] b() at 0x40000 |
[5] a() at 0x50000 |
[5] a() at 0x50000 |
[5] a() at 0x50000 |
如果所有这些块均导致内存泄漏,则 m 值决定泄漏按单独泄漏还是一个重复泄漏来报告。如果 m 为 2,则块 1 和块 2 按一个重复泄漏来报告,因为两个调用序列 malloc() 上的 2 个栈帧相同。块 3 将按单独泄漏来报告,因为 c() 的跟踪与其他块不匹配。如果 m 大于 2,运行时检查会将所有泄漏按单独泄漏来报告。(malloc 不显示在泄漏报告中。)
一般情况下,m 值越小,生成的单个泄漏报告越少,合并泄漏报告越多。m 值越大,生成的合并泄漏报告越少,单个泄漏报告越多。
获得内存泄漏报告后,按下列指导修复内存泄漏。
最重要的是确定泄漏位置。泄漏报告会提供泄漏块的分配跟踪,即泄漏块的分配位置。
然后可以查看程序的执行流程,了解块的使用情况。如果指针丢失位置很明显,便很好解决;否则,可以使用 showleaks 来缩小泄漏时段。缺省情况下,showleaks 命令仅提供自上次执行 showleaks 命令以来生成的新泄漏。可以在单步执行程序的同时重复运行 showleaks 以缩小内存块泄漏时段。
有关更多信息,请参见showleaks 命令。