Valgrind内存泄漏分析测试

上一篇介绍了Valgrind的基本信息,本文介绍如何使用Valgrind检测内存泄漏。

用法

1
valgrind --tool=memcheck --leak-check=full ./test

但是valgrind默认使用memcheck,所以可以省略

1
valgrind --leak-check=full ./test

数组越界/内存未释放

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdlib.h>
void k(void)
{
int *x = malloc(8 * sizeof(int));
x[9] = 0; // 数组下标越界
} // 内存未释放

int main(void)
{
k();
return 0;
}

编译

1
gcc -Wall test.c -g -o test
  • -o 输出
  • -g debug版
  • -Wall 提示所有警告

运行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
➜  code valgrind --leak-check=full ./test
==9289== Memcheck, a memory error detector
==9289== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==9289== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==9289== Command: ./test
==9289==
==9289== Invalid write of size 4
==9289== at 0x10916B: k (unrelease.c:5)
==9289== by 0x109180: main (unrelease.c:10)
==9289== Address 0x4a86064 is 4 bytes after a block of size 32 alloc'd
==9289== at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==9289== by 0x10915E: k (unrelease.c:4)
==9289== by 0x109180: main (unrelease.c:10)
==9289==
==9289==
==9289== HEAP SUMMARY:
==9289== in use at exit: 32 bytes in 1 blocks
==9289== total heap usage: 1 allocs, 0 frees, 32 bytes allocated
==9289==
==9289== 32 bytes in 1 blocks are definitely lost in loss record 1 of 1
==9289== at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==9289== by 0x10915E: k (unrelease.c:4)
==9289== by 0x109180: main (unrelease.c:10)
==9289==
==9289== LEAK SUMMARY:
==9289== definitely lost: 32 bytes in 1 blocks
==9289== indirectly lost: 0 bytes in 0 blocks
==9289== possibly lost: 0 bytes in 0 blocks
==9289== still reachable: 0 bytes in 0 blocks
==9289== suppressed: 0 bytes in 0 blocks
==9289==
==9289== For lists of detected and suppressed errors, rerun with: -s
==9289== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)

提示第5行无法写入,分配了一次内存但是没有释放。

修复代码错误

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdlib.h>
void k(void)
{
int *x = malloc(8 * sizeof(int));
x[7] = 0;
free(x);
}

int main(void)
{
k();
return 0;
}

再次测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
➜  code valgrind --leak-check=full ./test
==10526== Memcheck, a memory error detector
==10526== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==10526== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==10526== Command: ./test
==10526==
==10526==
==10526== HEAP SUMMARY:
==10526== in use at exit: 0 bytes in 0 blocks
==10526== total heap usage: 1 allocs, 1 frees, 32 bytes allocated
==10526==
==10526== All heap blocks were freed -- no leaks are possible
==10526==
==10526== For lists of detected and suppressed errors, rerun with: -s
==10526== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

内存释放后读写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
char *p = malloc(1); // 分配
*p = 'a';

char c = *p;

printf("\n [%c]\n", c);

free(p); // 释放
c = *p; // 取值
return 0;
}

测试结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
➜  code valgrind --leak-check=full ./test
==12246== Memcheck, a memory error detector
==12246== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==12246== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==12246== Command: ./test
==12246==

[a]
==12246== Invalid read of size 1
==12246== at 0x1091DE: main (release.c:14)
==12246== Address 0x4a86040 is 0 bytes inside a block of size 1 free'd
==12246== at 0x484B27F: free (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==12246== by 0x1091D9: main (release.c:13)
==12246== Block was alloc'd at
==12246== at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==12246== by 0x10919E: main (release.c:6)
==12246==
==12246==
==12246== HEAP SUMMARY:
==12246== in use at exit: 0 bytes in 0 blocks
==12246== total heap usage: 2 allocs, 2 frees, 1,025 bytes allocated
==12246==
==12246== All heap blocks were freed -- no leaks are possible
==12246==
==12246== For lists of detected and suppressed errors, rerun with: -s
==12246== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

无效读写

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
char *p = malloc(1); // 分配1字节
*p = 'a';
char c = *(p + 1); // 地址加1
printf("\n [%c]\n", c);
free(p);
return 0;
}

测试结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
➜  code valgrind --leak-check=full ./test
==14592== Memcheck, a memory error detector
==14592== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==14592== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==14592== Command: ./test
==14592==
==14592== Invalid read of size 1
==14592== at 0x1091AE: main (access.c:8)
==14592== Address 0x4a86041 is 0 bytes after a block of size 1 alloc'd
==14592== at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==14592== by 0x10919E: main (access.c:6)
==14592==

[]
==14592==
==14592== HEAP SUMMARY:
==14592== in use at exit: 0 bytes in 0 blocks
==14592== total heap usage: 2 allocs, 2 frees, 1,025 bytes allocated
==14592==
==14592== All heap blocks were freed -- no leaks are possible
==14592==
==14592== For lists of detected and suppressed errors, rerun with: -s
==14592== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

内存泄漏

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
int *p = malloc(1);
*p = 'x';
char c = *p;
printf("%c\n", c); // 申请后未释放
return 0;
}

测试结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
➜  code valgrind --leak-check=full ./test
==15397== Memcheck, a memory error detector
==15397== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==15397== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==15397== Command: ./test
==15397==
==15397== Invalid write of size 4
==15397== at 0x109187: main (leakage.c:7)
==15397== Address 0x4a86040 is 0 bytes inside a block of size 1 alloc'd
==15397== at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==15397== by 0x10917E: main (leakage.c:6)
==15397==
x
==15397==
==15397== HEAP SUMMARY:
==15397== in use at exit: 1 bytes in 1 blocks
==15397== total heap usage: 2 allocs, 1 frees, 1,025 bytes allocated
==15397==
==15397== 1 bytes in 1 blocks are definitely lost in loss record 1 of 1
==15397== at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==15397== by 0x10917E: main (leakage.c:6)
==15397==
==15397== LEAK SUMMARY:
==15397== definitely lost: 1 bytes in 1 blocks
==15397== indirectly lost: 0 bytes in 0 blocks
==15397== possibly lost: 0 bytes in 0 blocks
==15397== still reachable: 0 bytes in 0 blocks
==15397== suppressed: 0 bytes in 0 blocks
==15397==
==15397== For lists of detected and suppressed errors, rerun with: -s
==15397== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)

内存多次释放

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
char *p;
p = (char *)malloc(100);
if (p)
printf("Memory Allocated at: %s/n", p);
else
printf("Not Enough Memory!/n");
free(p); // 重复释放
free(p);
free(p);
return 0;
}

测试结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
➜  code valgrind --leak-check=full ./test
==17704== Memcheck, a memory error detector
==17704== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==17704== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==17704== Command: ./test
==17704==
==17704== Conditional jump or move depends on uninitialised value(s)
==17704== at 0x484ED19: strlen (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==17704== by 0x48D1DB0: __vfprintf_internal (vfprintf-internal.c:1517)
==17704== by 0x48BB81E: printf (printf.c:33)
==17704== by 0x1091C4: main (releases.c:8)
==17704==
==17704== Invalid free() / delete / delete[] / realloc()
==17704== at 0x484B27F: free (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==17704== by 0x1091F2: main (releases.c:12)
==17704== Address 0x4a86040 is 0 bytes inside a block of size 100 free'd
==17704== at 0x484B27F: free (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==17704== by 0x1091E6: main (releases.c:11)
==17704== Block was alloc'd at
==17704== at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==17704== by 0x10919E: main (releases.c:6)
==17704==
==17704== Invalid free() / delete / delete[] / realloc()
==17704== at 0x484B27F: free (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==17704== by 0x1091FE: main (releases.c:13)
==17704== Address 0x4a86040 is 0 bytes inside a block of size 100 free'd
==17704== at 0x484B27F: free (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==17704== by 0x1091E6: main (releases.c:11)
==17704== Block was alloc'd at
==17704== at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==17704== by 0x10919E: main (releases.c:6)
==17704==
Memory Allocated at: /n==17704==
==17704== HEAP SUMMARY:
==17704== in use at exit: 0 bytes in 0 blocks
==17704== total heap usage: 2 allocs, 4 frees, 1,124 bytes allocated
==17704==
==17704== All heap blocks were freed -- no leaks are possible
==17704==
==17704== Use --track-origins=yes to see where uninitialised values come from
==17704== For lists of detected and suppressed errors, rerun with: -s
==17704== ERROR SUMMARY: 3 errors from 3 contexts (suppressed: 0 from 0)

动态内存

常见的内存分配方式分三种:静态存储,栈上分配,堆上分配。全局变量属于静态存储,它们是在编译时就被分配了存储空间,函数内的局部变量属于栈上分配,而最灵活的内存使用方式当属堆上分配,也叫做内存动态分配了。常用的内存动态分配函数包括:malloc, alloc, realloc, new等,动态释放函数包括free, delete。
一旦成功申请了动态内存,我们就需要自己对其进行内存管理,而这又是最容易犯错误的。下面的一段程序,就包括了内存动态管理中常见的错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
int i;
char *p = (char *)malloc(10);
char *pt = p;
for (i = 0; i < 10; i++)
{
p[i] = 'z';
}
free(p);
pt[1] = 'x';
free(pt);
return 0;
}

测试结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
➜  code valgrind --leak-check=full ./test
==18498== Memcheck, a memory error detector
==18498== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==18498== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==18498== Command: ./test
==18498==
==18498== Invalid write of size 1
==18498== at 0x1091C9: main (new.c:13)
==18498== Address 0x4a86041 is 1 bytes inside a block of size 10 free'd
==18498== at 0x484B27F: free (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==18498== by 0x1091C0: main (new.c:12)
==18498== Block was alloc'd at
==18498== at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==18498== by 0x109185: main (new.c:6)
==18498==
==18498== Invalid free() / delete / delete[] / realloc()
==18498== at 0x484B27F: free (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==18498== by 0x1091D7: main (new.c:14)
==18498== Address 0x4a86040 is 0 bytes inside a block of size 10 free'd
==18498== at 0x484B27F: free (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==18498== by 0x1091C0: main (new.c:12)
==18498== Block was alloc'd at
==18498== at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==18498== by 0x109185: main (new.c:6)
==18498==
==18498==
==18498== HEAP SUMMARY:
==18498== in use at exit: 0 bytes in 0 blocks
==18498== total heap usage: 1 allocs, 2 frees, 10 bytes allocated
==18498==
==18498== All heap blocks were freed -- no leaks are possible
==18498==
==18498== For lists of detected and suppressed errors, rerun with: -s
==18498== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)

申请内存在使用完成后就要释放。如果没有释放,或少释放了就是内存泄露;多释放也会产生问题。上述程序中,指针p和pt指向的是同一块内存,却被先后释放两次。系统会在堆上维护一个动态内存链表,如果被释放,就意味着该块内存可以继续被分配给其他部分,如果内存被释放后再访问,就可能覆盖其他部分的信息,这是一种严重的错误,上述程序第14行中就在释放后仍然写这块内存。
输出结果显示,第13行分配和释放函数不一致;第14行发生非法写操作,也就是往释放后的内存地址写值;第15行释放内存函数无效。

内存重叠

C 语言的强大和可怕之处在于其可以直接操作内存,C 标准库中提供了大量这样的函数,比如 strcpy, strncpy, memcpy, strcat 等,这些函数有一个共同的特点就是需要设置源地址 (src),和目标地址(dst),src 和 dst 指向的地址不能发生重叠,否则结果将不可预期。

下面就是一个 src 和 dst 发生重叠的例子。在 15中,src 和 dst 所指向的地址相差 20,但指定的拷贝长度却是 21,这样就会把之前的拷贝值覆盖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

int main()
{
char x[50];
int i;

for (i = 0; i < 50; i++)
{
x[i] = i + 1;
}

strncpy(x + 20, x, 20);
strncpy(x + 20, x, 21);

return 0;
}

测试结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
➜  valgrind valgrind --leak-check=full ./test
==20940== Memcheck, a memory error detector
==20940== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==20940== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==20940== Command: ./test
==20940==
==20940== Source and destination overlap in strncpy(0x1ffefffae9, 0x1ffefffad5, 21)
==20940== at 0x484F084: strncpy (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==20940== by 0x1091DF: main (1.c:16)
==20940==
==20940==
==20940== HEAP SUMMARY:
==20940== in use at exit: 0 bytes in 0 blocks
==20940== total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==20940==
==20940== All heap blocks were freed -- no leaks are possible
==20940==
==20940== For lists of detected and suppressed errors, rerun with: -s
==20940== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

输出结果显示上述程序中第15,源地址和目标地址设置出现重叠。准确的发现了上述问题。


Valgrind内存泄漏分析测试
https://blog.jackeylea.com/valgrind/test-of-valgrind/
作者
JackeyLea
发布于
2024年3月23日
许可协议