Reputation: 43
I am trying to understand the behavior of printf in this example. of course the main issue here is that we are returning a pointer to a value on the stack that was popped after the function Boo returned.
I compiled with gcc. In test1: I got 7 printed twice which was expected. And a garbage value on the second printf in test2. but when I compiled with gcc -O3 I got 0 printed on both cases and a compiler warning about returning address of local variable.
test.c: In function ‘Foo’: test.c:8:12: warning: function returns address of local variable [-Wreturn-local-addr] return t; ^ test.c:5:9: note: declared here int j;
Can someone help me explain how does the behavior of printf that causes this behavior?
int *Boo(int i, int *p)
{
int j;
int *t = &j;
*t = i + *p;
return t;
}
void Foo(int x)
{
if (x == 0) { return;}
Foo(x - 1);
}
//test1
int main(void)
{
int x = 5;
int *t = Boo(2, &x);
printf("%d", *t);
printf("%d", *t);
return 0;
}
//test2
int main(void)
{
int x = 5;
int *t = Boo(2, &x);
printf("%d", *t);
Foo(8);
printf("%d", *t);
return 0;
}
Upvotes: 0
Views: 154
Reputation: 58663
GCC sees that Boo
returns a pointer to a local variable, and that therefore any attempt to use this pointer is undefined behavior. That means, according to the C standard, that the compiler can do whatever it wants, and in such cases GCC will often generate code that's "efficient" even if it is totally unrelated to what the programmer may have intended. It inlines the call to Boo
, which has no visible effects and therefore is optimized away, and it picks an "efficient" way to provide an argument for printf
, which happens to be the constant 0.
See on godbolt. The integer argument to printf
goes in esi
, which comes from ebp
which is zeroed. I guess there's a missed optimization in that it saves the same 0 in ebp
across the call to printf
to reload into esi
, instead of just re-zeroing esi
afterward. But the whole analysis is kind of pointless since, again, the behavior is undefined.
Boo
itself is optimized into a function that just returns a NULL
pointer and does nothing else, which again is legal because of undefined behavior, but that version of Boo
is not called by the program. Foo
is also optimized into a function which returns immediately without recursing; the compiler can tell that the recursive calls have no effect. (And if a negative argument were passed to Foo
, you'd have undefined behavior due to signed integer overflow, so the compiler need not handle that case.)
Upvotes: 0