Consider the following program.
#include <stdio.h> int negative(int A) { return (A & 0x80000000) != 0; } int divide(int A, int B) { printf("A = %d\n", A); printf("negative(A) = %d\n", negative(A)); if (negative(A)) { A = ~A + 1; printf("A = %d\n", A); printf("negative(A) = %d\n", negative(A)); } if (A < B) return 0; return 1; } int main(){ divide(-2147483648, -1); }
When it is compiled without compiler optimizations, it produces expected results.
gcc -Wall -Werror -g -o TestNegative TestNegative.c ./TestNegative A = -2147483648 negative(A) = 1 A = -2147483648 negative(A) = 1
When it is compiled with compiler optimizations, it produces the following incorrect output.
gcc -O3 -Wall -Werror -g -o TestNegative TestNegative.c ./TestNegative A = -2147483648 negative(A) = 1 A = -2147483648 negative(A) = 0
I am running gcc version 5.4.0
.
Is there a change I can make in the source code to prevent the compiler from producing this behavior under -O3
?
Use the command-line option -O0 (-[capital o][zero]) to disable optimization, and -S to get assembly file. Look here to see more gcc command-line options. Show activity on this post.
GCC has a range of optimization levels, plus individual options to enable or disable particular optimizations. The overall compiler optimization level is controlled by the command line option -On, where n is the required optimization level, as follows: -O0 . (default).
GCC performs nearly all supported optimizations that do not involve a space-speed tradeoff. As compared to -O , this option increases both compilation time and the performance of the generated code.
The compiler optimizes to reduce the size of the binary instead of execution speed. If you do not specify an optimization option, gcc attempts to reduce the compilation time and to make debugging always yield the result expected from reading the source code.
-2147483648
does not do what you think it does. C doesn't have negative constants. Include limits.h
and use INT_MIN
instead (pretty much every INT_MIN
definition on two's complement machines defines it as (-INT_MAX - 1)
for a good reason).
A = ~A + 1;
invokes undefined behavior because ~A + 1
causes integer overflow.
It's not the compiler, it's your code.
The compiler replaces your A = ~A + 1;
statement with a single neg
instruction, i.e. this code:
int just_negate(int A) { A = ~A + 1; return A; }
will be compiled to:
just_negate(int): mov eax, edi neg eax // just negate the input parameter ret
But the compiler is also smart enough to realize that, if A & 0x80000000
was non-zero before negation, it must be zero after negation, unless you are relying on undefined behavior.
This means that the second printf("negative(A) = %d\n", negative(A));
can be "safely" optimized to:
mov edi, OFFSET FLAT:.LC0 // .string "negative(A) = %d\n" xor eax, eax // just set eax to zero call printf
I use the online godbolt compiler explorer to check the assembly for various compiler optimizations.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With