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
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...
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.