当使用gcc-5.2.0编译程序时,为什么valgrind没有发现泄漏

今天我正在编写一些东西,在我完成之后,我与valgrind进行了检查,我得到了一个惊喜。

如果我使用gcc-4.9.2在我的Ubuntu(15.04 64BIT)上编译我的程序,如下所示:

 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个allocs,0个frees,0个字节分配

我试过的代码如下:

 #include #include #include 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。

现在我的问题是:是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在头文件中有strcpystrlen的实现。

检测内存泄漏的最佳方法是在不进行优化的情况下进行编译。 尝试使用-O0而不是-O2重新编译它。 在这种情况下,编译器将尽可能靠近源代码创建二进制文件。

有了这个:

printf(“String2 =%s \ n”,string2);

发现泄漏:

这似乎编译器检测到对string2依赖,因此它不会优化它。 可能是因为printf的实现在源代码的编译时不可用,或者因为printf使用的是variadic变量。 但这只是我猜…

继续我们在Will C中的评论中的讨论会自动释放没有指针的内存吗?valgrind输出的差异是编译器优化-O2优化代码分配的结果。 为什么? 我们来看看你的代码:

 string2 = malloc(33); strcpy (string2, "Hello"); ... printf("Bye\n"); 

虽然你已经为string2分配了内存并且你已经将"Hello"复制到了sting2 ,但你从不在代码的其余部分使用 string2 。 由于没有后续操作依赖于string2指向的内存或其中包含的值,因此编译器可以完全从最终的可执行文件中删除该代码。

“优化”中 ,编译器寻找可以使用更少的指令更有效地运行代码的方法,同时仍然提供相同的function。 由于没有任何东西依赖于与string2相关联的内存或值,因此编译器只是简单地断定代码可以更快地运行并且在更少的指令中如果它只是忽略分配并完全复制。

(这就是为什么正如在另一个答案中建议的那样,当你使用string2调用printf时出现泄漏,编译器不能简单地优化分配并复制掉,因为printf取决于string2的内存和值)

validation正在发生的事情的关键是查看编译器生成的汇编代码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 

现在,让我们看一下优化版本(使用-Ofast优化的gcc (GCC) 6.1.1 20160602 ):

  .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之间有所不同,但底线是相同的。 如果你声明,分配但从不使用任何值,编译器可以自由地优化该声明/分配,并且找到正在发生的事情的一种方法是查看汇编文件。 如果要重现程序集,则使用的完整编译字符串为:

 gcc -S -masm=intel -Ofast -o valgrindtest.asm valgrindtest.c 

然后看看valgrindtest.asm 。 希望这为你增添了另一块拼图。