2

Today I was coding something and after I was done, I made a check with valgrind and I got a surprise.

If I compile my program on my Ubuntu (15.04 64BIT) with gcc-4.9.2 with the following:

gcc -Wextra -Werror -Wstrict-prototypes -Wconversion --std=c11 -O2 -g program.c -o program

And then run valgrind:

valgrind --leak-check=full --track-origins=yes ./program

I get the following output:

==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)

As you can see the leak is spotted, but take a look of what happens if I compile with gcc-5.2.0 with the following:

./install/gcc-5.2.0/bin/gcc5.2 -Wextra -Werror -Wstrict-prototypes -Wconversion --std=c11 -O2 -g program.c -o program

And now valgrind says:

==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)

As you can see there is total heap usage: 0 allocs, 0 frees, 0 bytes allocated

The piece of code I tried was the following:

#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 was installed using this method.

Now my question is: is it GCC or valgrind at fault? Why does this happen and how can I avoid it?

One last thing, if I change:

printf("Bye\n");

to this:

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

The leak is spotted:

==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)

Which makes me ask myself why? Somehow printf() helps in this story.

Community
  • 1
  • 1
Michi
  • 5,175
  • 7
  • 33
  • 58
  • 1
    Does valgrind output change if you compile without optimizations (pass `-O0` instead `-O2`)? – SergA Oct 29 '15 at 11:10
  • 3
    sounds like gcc 5.2.0 is able to optimize the `malloc()` away (string2 is filled constantly and only used in `strlen()` which could be replaced by constant 5) – Ingo Leonhardt Oct 29 '15 at 11:10
  • @SergA YES, if i change **-O0 instead -O2** the leak is spotted. Why ? – Michi Oct 29 '15 at 11:12
  • @SergA with **GCC-4.9.2** using **-O2** the leak is spotted, strange. – Michi Oct 29 '15 at 11:19
  • 1
    @Michi No, it's not strange. gcc 5.2 is better at optimizing. It optimizes your malloc() call away. Since there is no call to malloc() there can't be a memory leak. You can add the -fno-builtin-malloc compiler flag if you want the code to still leak memory. – nos Oct 29 '15 at 11:25
  • as @IngoLeonhardt wrote most likely this is due to the improvement of the optimizer in newer gcc – SergA Oct 29 '15 at 11:26
  • @SergA So you suggest that if I compile with **GCC-5.2.0** to use ** -O0 instead -O2** ? – Michi Oct 29 '15 at 11:26
  • @IngoLeonhardt You mean that in this scenario (with **gcc-5.2.0**) is no memory leak ? – Michi Oct 29 '15 at 11:28
  • @Michi, my suggest is to free all allocated memory in anyway. Add `free(string2);` before returning from main. – SergA Oct 29 '15 at 11:30
  • @SergA I always FREE what I malloc, but i got myself confused when I made that check and I'm not able to understand it. That's all :). – Michi Oct 29 '15 at 11:33
  • Yes indeed i think there is no leak because there is no `malloc()` – Ingo Leonhardt Oct 29 '15 at 11:42
  • @IngoLeonhardt in my code you mean ? Because there is malloc **string2 = malloc(33);** – Michi Oct 29 '15 at 11:45
  • But after optimization that piece of code simply doesn't exist anymore – Ingo Leonhardt Oct 29 '15 at 11:49
  • @IngoLeonhardt Could you please post an Answer with an Explanation, because I found nothing on Internet related to this. Thank you. – Michi Oct 29 '15 at 11:51

2 Answers2

2

Seems that GCC 5.2.0 is able to detect that string2 is a constant "Hello" through the strcpy. So it just optimizes out string2 without allocating new memory chunk in the HEAP. My guess would be that string.h has the implementation of strcpy and strlen in the header itself.

The best way to detect memory leaks is to compile without optimizations. Try recompiling it with -O0 instead of -O2. In this case the compiler will create the binary as close to your source code as possible.

With this:

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

The leak is spotted:

Here it seems that the compiler detects dependency on string2 so it doesn't optimize it out. Probably because the implementation of printf is not available at the compilation time of your source or maybe because printf uses variadic variable. But it is just my guess...

Alex Lop.
  • 6,810
  • 1
  • 26
  • 45
  • I accepted your answer and I will try to read more about **GCC-5.2**. I need to know if is true or not about your Answer. – Michi Oct 29 '15 at 12:01
  • @Michi If you have it installed on your system, try to search for "string.h" and look in it for `strcpy` implementation. If it doesn't contain the implementation then I can't see any good reason why it would optimize `string2` out. But anyway if you ever do some analysis on your code like code coverage, memory leaks, etc, you should **almost** always use compilation without optimization. – Alex Lop. Oct 29 '15 at 12:05
  • Based on your comment I understand that i should use **-O0** instead **-O2**. Did I understood right ? – Michi Oct 29 '15 at 12:07
  • @Michi For code coverage or memory leak detection the answer is yes. For performance analysis, for example, you should use the same flags as the final product is built. – Alex Lop. Oct 29 '15 at 12:09
  • It contains only the headers BUT there is an interesting include: `uapi/linux/string.h` and also do you know if __HAVE_ARCH_STRCPY is defined in your ENV? – Alex Lop. Oct 29 '15 at 12:18
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/93680/discussion-between-alex-lop-and-michi). – Alex Lop. Oct 29 '15 at 12:22
  • 1
    [This answer](http://stackoverflow.com/a/18713375/1983398) explains the `strcpy` optimization. – ssbssa Oct 30 '15 at 07:18
2

Continuing our discussion from the comments in Will C automatically free memory with no pointers?, the difference in the valgrind output is the result of the compiler optimization -O2 optimizing the allocation out of your code. Why? Let's look at your code:

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

While you have allocated memory for string2 and you have copied "Hello" to sting2, you never use string2 in the remainder of your code. Since there is no subsequent operation that relies on the memory pointed to by string2 or the value contained within it, the compiler is free to delete that code entirely from the final executable.

In "optimizing", the compiler looks for ways it can make the code run more efficiently, with fewer instructions, while still providing the same functionality. Since nothing relies on the memory or value associated with string2, the compiler simply concludes the code can run faster and in fewer instructions if it just ignores the allocation and copy completely.

(that is why as suggested in the other answer when you call printf using string2 the leak appears, the compiler cannot simply optimize the allocation and copy away, because the printf depends on the memory and value of string2)

The key to verifying what is taking place is to look at the assembly code produced by the compiler (gcc -S produces the assembly file, add the option -masm=intel to tell the compiler to output the assembly in intel format instead of ATT)

Let's start with optimizations disabled -O0. The salient part of the assembly produced is:

    .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

Now, let's look at the optimized version (with gcc (GCC) 6.1.1 20160602 using the -Ofast optimization):

    .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

There is no malloc at all -- it has been optimized away. And true to what optimization should do, it runs in much fewer instructions.

Now how valgrind reports what it sees will differ between valgrind versions and differ between OS's, but the bottom line is the same. If you declare, allocate, but never use any value, the compiler is free to optimize that declaration/allocation away, and one way to find out just what is happening, is to look at the assembly file. If you want to reproduce the assembly, the complete compile string used was:

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

Then just look at valgrindtest.asm. Hopefully this adds another piece of the puzzle for you.

Community
  • 1
  • 1
David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
  • Sorry for the late Answer. Yes I realized that and makes sense. Any way should be a conditional part there which involves printf and even if that condition it will be not used, the compiler knows about it and Valgrind will catch it. Good Answer Thank you. – Michi Jul 23 '16 at 12:19
  • Very interesting! Optimizing compilers keep progressing, but for some reason they don't care about improving the programmer's skills. The compiler was able to remove the call to `malloc()` but it did not bother to warn about the leak that was easy to detect at compile time. – chqrlie Dec 06 '16 at 23:15
  • @chqrlie Since I had this problem, I made a Script which compiles my codes with all optimizations and then I run `Valgrind` to see the differences. – Michi Dec 06 '16 at 23:17