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.
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:While you have allocated memory for
string2
and you have copied"Hello"
tosting2
, you never usestring2
in the remainder of your code. Since there is no subsequent operation that relies on the memory pointed to bystring2
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
usingstring2
the leak appears, the compiler cannot simply optimize the allocation and copy away, because theprintf
depends on the memory and value ofstring2
)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 inintel
format instead ofATT
)Let's start with optimizations disabled
-O0
. The salient part of the assembly produced is:Now, let's look at the optimized version (with
gcc (GCC) 6.1.1 20160602
using the-Ofast
optimization):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:Then just look at
valgrindtest.asm
. Hopefully this adds another piece of the puzzle for you.Seems that GCC 5.2.0 is able to detect that
string2
is a constant"Hello"
through thestrcpy
. So it just optimizes outstring2
without allocating new memory chunk in the HEAP. My guess would be thatstring.h
has the implementation ofstrcpy
andstrlen
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.Here it seems that the compiler detects dependency on
string2
so it doesn't optimize it out. Probably because the implementation ofprintf
is not available at the compilation time of your source or maybe becauseprintf
uses variadic variable. But it is just my guess...