为什么在使用 gcc-5.2.0 编译程序时 valgrind 没有发现泄漏
Why doesn't valgrind spot the leak when program was compiled with gcc-5.2.0
今天我在写一些东西,完成后,我用 valgrind
进行了检查,我得到了一个惊喜。
如果我在 Ubuntu (15.04 64BIT) 上用 gcc-4.9.2 编译我的程序,如下:
gcc -Wextra -Werror -Wstrict-prototypes -Wconversion --std=c11 -O2 -g program.c -o program
然后 运行 valgrind:
valgrind --leak-check=full --track-origins=yes ./program
我得到以下输出:
==5325== Memcheck, a memory error detector
==5325== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==5325== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info
==5325== Command: ./program
==5325==
Bye
==5325==
==5325== HEAP SUMMARY:
==5325== in use at exit: 33 bytes in 1 blocks
==5325== total heap usage: 1 allocs, 0 frees, 33 bytes allocated
==5325==
==5325== 33 bytes in 1 blocks are definitely lost in loss record 1 of 1
==5325== at 0x4C2BBA0: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==5325== by 0x4004BD: main (program.c:11)
==5325==
==5325== LEAK SUMMARY:
==5325== definitely lost: 33 bytes in 1 blocks
==5325== indirectly lost: 0 bytes in 0 blocks
==5325== possibly lost: 0 bytes in 0 blocks
==5325== still reachable: 0 bytes in 0 blocks
==5325== suppressed: 0 bytes in 0 blocks
==5325==
==5325== For counts of detected and suppressed errors, rerun with: -v
==5325== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
如您所见,泄漏已被发现,但看看如果我使用 gcc-5.2.0 编译时会发生什么:
./install/gcc-5.2.0/bin/gcc5.2 -Wextra -Werror -Wstrict-prototypes -Wconversion --std=c11 -O2 -g program.c -o program
现在 valgrind 说:
==5344== Memcheck, a memory error detector
==5344== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==5344== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info
==5344== Command: ./program
==5344==
Bye
==5344==
==5344== HEAP SUMMARY:
==5344== in use at exit: 0 bytes in 0 blocks
==5344== total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==5344==
==5344== All heap blocks were freed -- no leaks are possible
==5344==
==5344== For counts of detected and suppressed errors, rerun with: -v
==5344== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
如您所见,总堆使用量:0 次分配,0 次释放,0 字节分配
我试过的代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(void){
int a = 0;
size_t len1 = 0, len2 = 0;
char *string1 = "Hello";
char *string2;
string2 = malloc(33);
strcpy(string2, "Hello");
len1 = strlen(string1);
len2 = strlen(string2);
if(len1 != len2){
a = 5;
}else{
a=4;
}
while (a != -1){
if(a == 2){
break;
}
a--;
}
printf("Bye\n");
/*free(string2);*/
return 0;
}
已安装 GCC-5.2.0 using this method。
现在我的问题是:是 GCC 还是 valgrind 出了问题?为什么会发生这种情况,我该如何避免?
最后一件事,如果我改变:
printf("Bye\n");
对此:
printf("String2 = %s\n",string2);
发现泄漏:
==5443== Memcheck, a memory error detector
==5443== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==5443== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info
==5443== Command: ./program
==5443==
String2 = Hello
==5443==
==5443== HEAP SUMMARY:
==5443== in use at exit: 33 bytes in 1 blocks
==5443== total heap usage: 1 allocs, 0 frees, 33 bytes allocated
==5443==
==5443== 33 bytes in 1 blocks are definitely lost in loss record 1 of 1
==5443== at 0x4C2BBA0: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==5443== by 0x40044D: main (program.c:11)
==5443==
==5443== LEAK SUMMARY:
==5443== definitely lost: 33 bytes in 1 blocks
==5443== indirectly lost: 0 bytes in 0 blocks
==5443== possibly lost: 0 bytes in 0 blocks
==5443== still reachable: 0 bytes in 0 blocks
==5443== suppressed: 0 bytes in 0 blocks
==5443==
==5443== For counts of detected and suppressed errors, rerun with: -v
==5443== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
这让我问自己为什么? printf() 以某种方式帮助了这个故事。
似乎 GCC 5.2.0 能够通过 strcpy
检测到 string2
是一个常量 "Hello"
。所以它只是优化了 string2
而没有在 HEAP 中分配新的内存块。我的猜测是 string.h
在 header 本身中实现了 strcpy
和 strlen
。
检测内存泄漏的最佳方法是在不进行优化的情况下进行编译。尝试使用 -O0
而不是 -O2
重新编译它。在这种情况下,编译器将创建尽可能接近您的源代码的二进制文件。
With this:
printf("String2 = %s\n",string2);
The leak is spotted:
这里编译器似乎检测到对 string2
的依赖,所以它没有优化它。 可能是因为 printf
的实现在您的源代码编译时不可用,或者可能是因为 printf
使用可变参数。但这只是我的猜测...
继续我们在 中的评论中的讨论,valgrind
输出中的差异是编译器优化 -O2
优化代码分配的结果。为什么?让我们看看您的代码:
string2 = malloc(33);
strcpy (string2, "Hello");
...
printf("Bye\n");
虽然您已经为 string2
分配了内存并且您已经将 "Hello"
复制到 sting2
,但 您永远不会使用 string2
在您的代码的其余部分。由于没有依赖于 string2
指向的内存或其中包含的值的后续操作,因此编译器可以自由地从最终可执行文件中完全删除该代码。
在 "optimizing" 中,编译器寻找可以使代码 运行 更高效、指令更少,同时仍提供相同功能的方法.由于没有任何东西依赖于与 string2
关联的内存或值,编译器简单地得出结论,如果它完全忽略分配和复制,则代码可以 运行 更快,指令更少。
(这就是为什么当您使用 string2
调用 printf
时出现泄漏,编译器不能简单地优化分配和复制,因为 printf
取决于 string2
)
的内存和值
验证正在发生的事情的关键是查看编译器生成的汇编代码(gcc -S
生成汇编文件,添加选项-masm=intel
告诉编译器以 intel
格式而不是 ATT
)
输出程序集
让我们从禁用优化开始 -O0
。生成的组件的显着部分是:
.cfi_startproc
push rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
mov rbp, rsp
.cfi_def_cfa_register 6
sub rsp, 48
mov DWORD PTR [rbp-4], 0
mov QWORD PTR [rbp-16], 0
mov QWORD PTR [rbp-24], 0
mov QWORD PTR [rbp-32], OFFSET FLAT:.LC0
mov QWORD PTR [rbp-40], 0
mov edi, 33
call malloc ; the call to malloc is retained
mov QWORD PTR [rbp-40], rax
mov rax, QWORD PTR [rbp-40]
mov DWORD PTR [rax], 1819043144
mov WORD PTR [rax+4], 111
mov rax, QWORD PTR [rbp-32]
mov rdi, rax
call strlen
mov QWORD PTR [rbp-16], rax
mov rax, QWORD PTR [rbp-40]
mov rdi, rax
call strlen
mov QWORD PTR [rbp-24], rax
mov rax, QWORD PTR [rbp-16]
cmp rax, QWORD PTR [rbp-24]
je .L2
mov DWORD PTR [rbp-4], 5
jmp .L4
现在,让我们看看优化后的版本(gcc (GCC) 6.1.1 20160602
使用 -Ofast
优化):
.cfi_startproc
sub rsp, 8
.cfi_def_cfa_offset 16
mov edi, OFFSET FLAT:.LC0
call puts
xor eax, eax
add rsp, 8
.cfi_def_cfa_offset 8
ret
.cfi_endproc
根本没有 malloc
-- 它已被优化掉。 优化 应该做到的是,它 运行 的指令要少得多。
现在 valgrind 如何报告它看到的内容在 valgrind
版本和 OS 版本之间会有所不同,但底线是相同的。如果您声明、分配但从不使用任何值,编译器可以自由地优化 declaration/allocation 掉,并且找出正在发生的事情的一种方法是查看汇编文件。如果要重现程序集,使用的完整编译字符串为:
gcc -S -masm=intel -Ofast -o valgrindtest.asm valgrindtest.c
那就看看valgrindtest.asm
。希望这能为您增加另一块拼图。
今天我在写一些东西,完成后,我用 valgrind
进行了检查,我得到了一个惊喜。
如果我在 Ubuntu (15.04 64BIT) 上用 gcc-4.9.2 编译我的程序,如下:
gcc -Wextra -Werror -Wstrict-prototypes -Wconversion --std=c11 -O2 -g program.c -o program
然后 运行 valgrind:
valgrind --leak-check=full --track-origins=yes ./program
我得到以下输出:
==5325== Memcheck, a memory error detector ==5325== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al. ==5325== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info ==5325== Command: ./program ==5325== Bye ==5325== ==5325== HEAP SUMMARY: ==5325== in use at exit: 33 bytes in 1 blocks ==5325== total heap usage: 1 allocs, 0 frees, 33 bytes allocated ==5325== ==5325== 33 bytes in 1 blocks are definitely lost in loss record 1 of 1 ==5325== at 0x4C2BBA0: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==5325== by 0x4004BD: main (program.c:11) ==5325== ==5325== LEAK SUMMARY: ==5325== definitely lost: 33 bytes in 1 blocks ==5325== indirectly lost: 0 bytes in 0 blocks ==5325== possibly lost: 0 bytes in 0 blocks ==5325== still reachable: 0 bytes in 0 blocks ==5325== suppressed: 0 bytes in 0 blocks ==5325== ==5325== For counts of detected and suppressed errors, rerun with: -v ==5325== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
如您所见,泄漏已被发现,但看看如果我使用 gcc-5.2.0 编译时会发生什么:
./install/gcc-5.2.0/bin/gcc5.2 -Wextra -Werror -Wstrict-prototypes -Wconversion --std=c11 -O2 -g program.c -o program
现在 valgrind 说:
==5344== Memcheck, a memory error detector ==5344== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al. ==5344== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info ==5344== Command: ./program ==5344== Bye ==5344== ==5344== HEAP SUMMARY: ==5344== in use at exit: 0 bytes in 0 blocks ==5344== total heap usage: 0 allocs, 0 frees, 0 bytes allocated ==5344== ==5344== All heap blocks were freed -- no leaks are possible ==5344== ==5344== For counts of detected and suppressed errors, rerun with: -v ==5344== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
如您所见,总堆使用量:0 次分配,0 次释放,0 字节分配
我试过的代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(void){
int a = 0;
size_t len1 = 0, len2 = 0;
char *string1 = "Hello";
char *string2;
string2 = malloc(33);
strcpy(string2, "Hello");
len1 = strlen(string1);
len2 = strlen(string2);
if(len1 != len2){
a = 5;
}else{
a=4;
}
while (a != -1){
if(a == 2){
break;
}
a--;
}
printf("Bye\n");
/*free(string2);*/
return 0;
}
已安装 GCC-5.2.0 using this method。
现在我的问题是:是 GCC 还是 valgrind 出了问题?为什么会发生这种情况,我该如何避免?
最后一件事,如果我改变:
printf("Bye\n");
对此:
printf("String2 = %s\n",string2);
发现泄漏:
==5443== Memcheck, a memory error detector ==5443== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al. ==5443== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info ==5443== Command: ./program ==5443== String2 = Hello ==5443== ==5443== HEAP SUMMARY: ==5443== in use at exit: 33 bytes in 1 blocks ==5443== total heap usage: 1 allocs, 0 frees, 33 bytes allocated ==5443== ==5443== 33 bytes in 1 blocks are definitely lost in loss record 1 of 1 ==5443== at 0x4C2BBA0: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==5443== by 0x40044D: main (program.c:11) ==5443== ==5443== LEAK SUMMARY: ==5443== definitely lost: 33 bytes in 1 blocks ==5443== indirectly lost: 0 bytes in 0 blocks ==5443== possibly lost: 0 bytes in 0 blocks ==5443== still reachable: 0 bytes in 0 blocks ==5443== suppressed: 0 bytes in 0 blocks ==5443== ==5443== For counts of detected and suppressed errors, rerun with: -v ==5443== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
这让我问自己为什么? printf() 以某种方式帮助了这个故事。
似乎 GCC 5.2.0 能够通过 strcpy
检测到 string2
是一个常量 "Hello"
。所以它只是优化了 string2
而没有在 HEAP 中分配新的内存块。我的猜测是 string.h
在 header 本身中实现了 strcpy
和 strlen
。
检测内存泄漏的最佳方法是在不进行优化的情况下进行编译。尝试使用 -O0
而不是 -O2
重新编译它。在这种情况下,编译器将创建尽可能接近您的源代码的二进制文件。
With this:
printf("String2 = %s\n",string2);
The leak is spotted:
这里编译器似乎检测到对 string2
的依赖,所以它没有优化它。 可能是因为 printf
的实现在您的源代码编译时不可用,或者可能是因为 printf
使用可变参数。但这只是我的猜测...
继续我们在 valgrind
输出中的差异是编译器优化 -O2
优化代码分配的结果。为什么?让我们看看您的代码:
string2 = malloc(33);
strcpy (string2, "Hello");
...
printf("Bye\n");
虽然您已经为 string2
分配了内存并且您已经将 "Hello"
复制到 sting2
,但 您永远不会使用 string2
在您的代码的其余部分。由于没有依赖于 string2
指向的内存或其中包含的值的后续操作,因此编译器可以自由地从最终可执行文件中完全删除该代码。
在 "optimizing" 中,编译器寻找可以使代码 运行 更高效、指令更少,同时仍提供相同功能的方法.由于没有任何东西依赖于与 string2
关联的内存或值,编译器简单地得出结论,如果它完全忽略分配和复制,则代码可以 运行 更快,指令更少。
(这就是为什么当您使用 string2
调用 printf
时出现泄漏,编译器不能简单地优化分配和复制,因为 printf
取决于 string2
)
验证正在发生的事情的关键是查看编译器生成的汇编代码(gcc -S
生成汇编文件,添加选项-masm=intel
告诉编译器以 intel
格式而不是 ATT
)
让我们从禁用优化开始 -O0
。生成的组件的显着部分是:
.cfi_startproc
push rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
mov rbp, rsp
.cfi_def_cfa_register 6
sub rsp, 48
mov DWORD PTR [rbp-4], 0
mov QWORD PTR [rbp-16], 0
mov QWORD PTR [rbp-24], 0
mov QWORD PTR [rbp-32], OFFSET FLAT:.LC0
mov QWORD PTR [rbp-40], 0
mov edi, 33
call malloc ; the call to malloc is retained
mov QWORD PTR [rbp-40], rax
mov rax, QWORD PTR [rbp-40]
mov DWORD PTR [rax], 1819043144
mov WORD PTR [rax+4], 111
mov rax, QWORD PTR [rbp-32]
mov rdi, rax
call strlen
mov QWORD PTR [rbp-16], rax
mov rax, QWORD PTR [rbp-40]
mov rdi, rax
call strlen
mov QWORD PTR [rbp-24], rax
mov rax, QWORD PTR [rbp-16]
cmp rax, QWORD PTR [rbp-24]
je .L2
mov DWORD PTR [rbp-4], 5
jmp .L4
现在,让我们看看优化后的版本(gcc (GCC) 6.1.1 20160602
使用 -Ofast
优化):
.cfi_startproc
sub rsp, 8
.cfi_def_cfa_offset 16
mov edi, OFFSET FLAT:.LC0
call puts
xor eax, eax
add rsp, 8
.cfi_def_cfa_offset 8
ret
.cfi_endproc
根本没有 malloc
-- 它已被优化掉。 优化 应该做到的是,它 运行 的指令要少得多。
现在 valgrind 如何报告它看到的内容在 valgrind
版本和 OS 版本之间会有所不同,但底线是相同的。如果您声明、分配但从不使用任何值,编译器可以自由地优化 declaration/allocation 掉,并且找出正在发生的事情的一种方法是查看汇编文件。如果要重现程序集,使用的完整编译字符串为:
gcc -S -masm=intel -Ofast -o valgrindtest.asm valgrindtest.c
那就看看valgrindtest.asm
。希望这能为您增加另一块拼图。