Reputation: 31
From What happens to a declared, uninitialized variable in C? Does it have a value?, I tried playing with Ciro Santilli's code, shown below.
int f() {
int i = 13;
return i;
}
int g() {
int i;
return i;
}
int main() {
assert(f() == 13);
assert(g() == 0);
}
The call to g()
should reuse the same address of the stack frame and the value of i
in g()
, although not initialized, should be 13. I have check the address of i
in both functions and the address is the same.
However, it's 13 only when I use g++ -O0
but not with other level such as -O1
or -O3
I test this on both Windows 10 (gcc 8.1.0) and Ubuntu 18.04 (gcc 7.5.0). To be precise:
g++ -O3 -o test test.cpp -std=c++11
produce no assertion fail.
while g++ -O0 -o test test.cpp -std=c++11
gives Assertion `g() == 0' failed.
I understand that using i
in g()
falls into undefined behavior
in the standard but it seems strange to me that other optimization level seems to go out of their way to change the value of i
in g()
from 13 back to 0. Am I missing something?
Upvotes: 2
Views: 709
Reputation: 7334
As eerorika's answer says, your code invokes undefined behavior.
If you actually look at the assembly code generated, you get this
f():
mov eax, 13
ret
g():
ret
main:
xor eax, eax
ret
As you can see g()
is a single ret
instruction, compared to f()
which sets eax
to 13. So the return value of g()
is whatever happens to be in the eax
register at the time.
The reason why you think that g()
returns 0 is that the assert doesn't fail. But that's because -O3
optimized out the assert calls altogether and essentially replaced the body of main with return 0;
.
Edit: That was actually clang's output. Using gcc g()
is compiled to this:
g():
xor eax, eax
ret
so in that case it actually is return 0;
:). I don't know why gcc does this, but you can't really reason about it since as mentioned before it is undefined behavior.
Upvotes: 3
Reputation: 4217
@eerorika is correct that this is undefined behavior and anything can happen. Though I was at least able to reproduce your issue so I can at least provide a possible reason this is happening for your specific example.
At -O0
, g
compiles to (on x64 GCC 11.2):
g:
push rbp
mov rbp, rsp
mov eax, DWORD PTR [rbp-4]
pop rbp
ret
The function is creating a local variable on the stack but doesn't assign it a value. It then returns this. You haven't specified what compiler or system you're using but you're likely to be experiencing similar behavior.
At -O3
, the function compiles to:
g:
xor eax, eax
ret
Here, the ompiler notices that it really doesn't need to create a new variable on the stack uselessly, so it zeroes out eax
and returns it. Since this is undefined behavior, the compiler can even leave out xor eax, eax
and not zero out the return value, though it doesn't seem like it is in your case. x64 Clang 12.0.0 does, however.
Even if it does though, both compilers optimize out the assert
s completely. This is perfectly fine, as since there is undefined behavior, the compiler is allowed to do whatever it wants. So it assumes the return value of g
will be 0
and optimizes the assert
out.
You should however check the assembly generated by your own compiler to confirm this.
Upvotes: 2
Reputation: 238461
Default initialised int has an indeterminate value. If you read an indeterminate value (such as if you return it), then the behaviour of the program is undefined (there are exceptions but none that apply to your program).
The behaviour that you observed is explained by the behaviour being undefined.
it seems strange to me that other optimization level seems to go out of their way to
If this seems strange you, then I suspect that you don't understand what undefined behaviour means.
It is also unclear why you think that the compiler "seems to go out of their way".
Upvotes: 2