3

The following code running compiler options -O3 vs -O0 results different output:

#include <stdlib.h>
#include <stdio.h>

int main(){
   int *p = (int*)malloc(sizeof(int));    
   int *q = (int*)realloc(p, sizeof(int));

   *p = 1;
   *q = 2;

   if (p == q)
     printf("%d %d", *p, *q);

   return 0;
}

I was very surprised with the outcome.

Compiling with clang 3.4, 3.5 (http://goo.gl/sDLvrq)

  • using compiler options -O0 — output: 2 2

  • using compiler options -O3 — output: 1 2

Is it a bug?

Interestingly if I modify the code slightly (http://goo.gl/QwrozF) it behaves as expected.

int *p = (int*)malloc(sizeof(int));    
*p = 1;

Testing it on gcc seems to work fine.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Tal Agmon
  • 33
  • 2
  • 9
    Once you pass `p` to `realloc` you shouldn't use it again. Doing so is undefined behavior. http://en.cppreference.com/w/c/memory/realloc – Retired Ninja Apr 03 '15 at 23:26
  • 3
    Yes; it is a bug. But it is a bug in your code, not in the compilers or the runtime libraries that they're using. – Jonathan Leffler Apr 04 '15 at 01:08
  • Choose a language. If it's C, don't cast the result of `malloc`. If it's C++, don't use `malloc` in the first place. In any case, `realloc` invalidates the original pointer `p`, so don't use it afterwards. – Mike Seymour Apr 04 '15 at 01:56
  • mildly interesting that it optimized out re-reading `p` but did not optimize out `if (p == q)` – M.M Apr 04 '15 at 02:57
  • @TalAgmon Even if the allocation is done in-place the pointer you passed is considered to be invalid. The bug in your code is using that pointer, it invokes undefined behavior and the compiler is free to do whatever it wishes. http://stackoverflow.com/questions/26072752/realloc-dangling-pointers-and-undefined-behavior and http://stackoverflow.com/questions/26073842/is-the-compiler-allowed-to-recycle-freed-pointer-variables might give you some additional insight. – Retired Ninja Apr 04 '15 at 08:35

2 Answers2

5

After the realloc, p is no longer valid.

Lee Daniel Crocker
  • 12,927
  • 3
  • 29
  • 55
  • More precisely, it is no longer guaranteed to be valid. It can be. That's why the return value of realloc() needs to be checked and appropriate action taken before dereferencing the original pointer. Without that, the behaviour is undefined. – Peter Apr 04 '15 at 00:22
  • 1
    @Peter It's definitely not valid. The Standard term is *indeterminate* . – M.M Apr 04 '15 at 00:27
  • `realloc` essentially does an automatic `free` on the original pointer, so it's definitely not valid. How the implementation handles it may mean it may or may not work, as we see here. Or rather, if successful. If `realloc` fails, the old pointer is valid. – teppic Apr 04 '15 at 00:31
  • Indeterminate is not quite the same as undefined. In this case, if p and q are equal (and non-NULL) then p can be safely dereferenced. It is indeterminate whether they WILL compare equal. There is nothing in the specification of realloc() that prevents it returning the same pointer (e.g. if the preceding call of malloc()/realloc() over-allocated enough, and that information is available to realloc(), it can safely return the same pointer without allocating new memory, copying, and releasing the old). – Peter Apr 04 '15 at 00:39
  • @Peter it's true that `realloc` can return a pointer to the same memory, but the standard explicitly says that the original memory is released on success and the pointer is invalid. The reason for keeping hold of the original pointer is in the case of a failure, as you'll need it to free the original memory. e.g. if you run `p = realloc(p, 1000)` and `realloc` fails, you've still got the old memory but you've overwritten the pointer to it with NULL. – teppic Apr 04 '15 at 00:41
  • @Peter it causes undefined behaviour to dereference `p` or try to compare it to `q` – M.M Apr 04 '15 at 02:55
3

Assuming both of the allocations are successful, q points to an allocated region of memory and p is an invalid pointer. The standard treats realloc and free as deallocation routines, and if successful, the address the pointer held can no longer be used. If the call to realloc fails for some reason, the original memory is still valid (but of course q isn't, it's NULL).

Although you compare p and q, you've already written to an invalid pointer, so all bets are off.

What's probably happening here is that the O3 setting is causing the compiler to ignore the pointers and just substitute numbers inline. High optimisation means a compiler can take all sorts of short cuts and ignore statements so long as it guarantees the same result - the condition being that all of the code is well defined.

teppic
  • 8,039
  • 2
  • 24
  • 37