https://gcc.gnu.org/bugzilla/show_bug.cgi?id=78228
Bug ID: 78228 Summary: fstrict-overflow breaks code without overflow? Product: gcc Version: 6.2.0 Status: UNCONFIRMED Severity: normal Priority: P3 Component: c Assignee: unassigned at gcc dot gnu.org Reporter: adl at gnu dot org Target Milestone: --- I'm compiling for x86_64. These two functions used to work fine in gcc-5.4.1, but behave incorrectly with gcc-6.2 when optimizations are turned on. The job is to compute the number of characters needed to display a signed integer when represented in base 10. Assuming int is 32bits, this is most efficiently done using a series of comparisons (int_width_7) that can even be vectorized (int_width_8). Positive input values are negated before the comparisons start; working with negative values instead of positive values is a way to *avoid* signed overflow when processing INT_MIN. Because i = -i is the only operation done on int, and it is only applied to positive values, I don't think there is any undefined behavior in the following code, isn't it? #include <stdio.h> #include <limits.h> __attribute__((noinline)) unsigned int_width_7(int i) { unsigned width = i < 0; if (i > 0) i = -i; if (i <= -1000000000) return width + 10; if (i <= -100000000) return width + 9; if (i <= -10000000) return width + 8; if (i <= -1000000) return width + 7; if (i <= -100000) return width + 6; if (i <= -10000) return width + 5; if (i <= -1000) return width + 4; if (i <= -100) return width + 3; if (i <= -10) return width + 2; return width + 1; } __attribute__((noinline)) unsigned int_width_8(int i) { unsigned width = i < 0; if (i > 0) i = -i; static const int v[] = { -1000000000, -100000000, -10000000, -1000000, -100000, -10000, -1000, -100, -10, 0 }; for (int vi = 0; vi < sizeof(v)/sizeof(*v); ++vi) width += (i <= v[vi]); return width; } int main() { printf("%d %d\n", int_width_7(INT_MIN), int_width_8(INT_MIN)); } % gcc-6 -Wall -O foo.c && ./a.out 11 11 % gcc-6 -Wall -O2 foo.c && ./a.out 2 11 % gcc-6 -Wall -O3 foo.c && ./a.out 2 10 The changes between -O and -O2 seems to be due to -fstrict-overflow: % gcc -Wall -O -fstrict-overflow foo.c && ./a.out 2 11 Looking into the assembly generated code for int_width_7, we can see that the following -O output int_width_7: .LFB11: .cfi_startproc movl %edi, %eax shrl $31, %eax movl %edi, %edx sarl $31, %edx xorl %edx, %edi subl %edi, %edx cmpl $-999999999, %edx jl .L12 cmpl $-99999999, %edx jl .L13 cmpl $-9999999, %edx jl .L14 cmpl $-999999, %edx jl .L15 cmpl $-99999, %edx jl .L16 cmpl $-9999, %edx jl .L17 cmpl $-999, %edx jl .L18 cmpl $-99, %edx jl .L19 leal 1(%rax), %ecx addl $2, %eax cmpl $-10, %edx cmovg %ecx, %eax ret becomes as follows once compiled with -O -fstrict-overflow: int_width_7: .LFB11: .cfi_startproc movl %edi, %eax shrl $31, %eax movl %edi, %edx sarl $31, %edx xorl %edx, %edi subl %edx, %edi cmpl $999999999, %edi jg .L12 cmpl $99999999, %edi jg .L13 cmpl $9999999, %edi jg .L14 cmpl $999999, %edi jg .L15 cmpl $99999, %edi jg .L16 cmpl $9999, %edi jg .L17 cmpl $999, %edi jg .L18 cmpl $99, %edi jg .L19 leal 2(%rax), %edx addl $1, %eax cmpl $10, %edi cmovge %edx, %eax ret Somehow, gcc decided to work with positive integers instead, even though that cannot work MIN_INT? I have not looked at the problem with int_width_8 in details: I can see that the constants are still negative, yet the result is clearly incorrect.