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

时间:2015-10-29 11:04:44

标签: c gcc memory memory-leaks 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个分配,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()有助于这个故事。

2 个答案:

答案 0 :(得分:2)

似乎GCC 5.2.0能够通过string2检测到"Hello"是常数strcpy。所以它只是优化string2而不在HEAP中分配新的内存块。我的猜测是string.h在标题本身中实现了strcpystrlen

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

  

有了这个:

     

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

     

发现泄漏:

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

答案 1 :(得分:2)

继续我们对Will C automatically free memory with no pointers?中的评论的讨论,valgrind输出的差异是编译器优化-O2优化代码分配的结果。为什么?我们来看看你的代码:

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

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

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

(这就是为什么当你使用printf调用string2时,在另一个答案中建议出现泄漏,编译器不能简单地优化分配并复制掉,因为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之间有所不同,但底线是相同的。如果你声明,分配,但从不使用任何值,编译器可以自由地优化该声明/分配,并且找到正在发生的事情的一种方法是查看汇编文件。如果要重现程序集,则使用的完整编译字符串为:

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

然后看看valgrindtest.asm。希望这为你增添了另一个难题。