Reputation: 51
I've written this C code for finding the sum of all integers which are equal to the sum of the factorial of their digits. It takes a minute or so to get the job done without any GCC optimization flags, using -O1 decreased that time by about 15-20 seconds but when I tried it with -O2, -O3 or -Os it gets stuck in an infinite loop.
int main()
{
int i, j, factorials[10];
int Result=0;
for(i=0; i<10; i++)
{
factorials[i]=1;
for(j=i; j>0; j--)
{
factorials[i] *= j;
}
}
for(i=3; i>2; i++) //This is the loop the program gets stuck on
{
int Sum=0, number=i;
while(number)
{
Sum += factorials[number % 10];
number /= 10;
}
if(Sum == i)
Result += Sum;
}
printf("%d\n", Result);
return 0;
}
I've pinpointed that for(i=3; i>2; i++)
is the cause of the problem. So obviously i
is never less than 2?
Does this have anything to do with the fact that integer overflow behavior is undefined? If so, any more info on what exactly is going on with the program in these cases?
EDIT: I guess I should've mentioned, I am aware of other ways of writing that for loop so that it doesn't use overflowing(I was hoping that INT_MAX+1 would be equal to INT_MIN which is <2) but this was just done as a random test to see what would happen and I posted it here to find out what exactly was going on :)
Upvotes: 4
Views: 5136
Reputation: 2096
I find very strange the differences between the assembler results of the following code compiled without optimization and with -Os optimization.
#include <stdio.h>
int main(){
int i;
for(i=3;i>2;i++);
printf("%d\n",i);
return 0;
}
Without optimization the code results:
000000000040052d <main>:
40052d: 55 push %rbp
40052e: 48 89 e5 mov %rsp,%rbp
400531: 48 83 ec 10 sub $0x10,%rsp
400535: c7 45 fc 03 00 00 00 movl $0x3,-0x4(%rbp)
40053c: c7 45 fc 03 00 00 00 movl $0x3,-0x4(%rbp)
400543: eb 04 jmp 400549 <main+0x1c>
400545: 83 45 fc 01 addl $0x1,-0x4(%rbp)
400549: 83 7d fc 02 cmpl $0x2,-0x4(%rbp)
40054d: 7f f6 jg 400545 <main+0x18>
40054f: 8b 45 fc mov -0x4(%rbp),%eax
400552: 89 c6 mov %eax,%esi
400554: bf f4 05 40 00 mov $0x4005f4,%edi
400559: b8 00 00 00 00 mov $0x0,%eax
40055e: e8 ad fe ff ff callq 400410 <printf@plt>
400563: b8 00 00 00 00 mov $0x0,%eax
400568: c9 leaveq
400569: c3 retq
and the output is: -2147483648 (as I expect on a PC)
With -Os the code results:
0000000000400400 <main>:
400400: eb fe jmp 400400 <main>
I think the second result is an error!!! I think the compiler should have compiled something corresponding to the code:
printf("%d\n",-2147483648);
Upvotes: 1
Reputation: 961
The for loop is for(i=3; i>2; i++)
and inside this loop i
is not modified, nor is there a break
or any other way to exit the loop. You are relying on integer overflow to cause the exit condition to occur, but the compiler doesn't take that into consideration.
Instead, the compiler sees that i
starts at 3, and i
is only ever incremented, and so i>2
is always true. Thus there is no need for i
to exist at all in this context, since this must be an infinite loop.
If you change i
to be unsigned int
and set the condition for the loop exit to match, this "optimization" will no longer occur.
Upvotes: 1
Reputation:
As you said, it's undefined behavior, so you can't rely on any particular behavior.
The two things you will most likely see are:
Upvotes: 0
Reputation: 141618
The loop is for (i = 3; i > 2; i++)
and it has no break
statements or other exit condition.
Eventually i
will reach INT_MAX
and then i++
will cause integer overflow which causes undefined behaviour.
Possibly Sum
or Result
would also overflow before i
did.
When a program is guaranteed to trigger undefined behaviour , the entire behaviour of the program is undefined.
gcc is well known for aggressively optimizing out paths that trigger UB . You could inspect the assembly code to see what exactly happened in your case. Perhaps the -O2
and higher cases removed the loop end condition check , but -O1
left it in there and "relied" on INT_MAX + 1
resulting in INT_MIN
.
Upvotes: 7
Reputation: 602
I don't know what are you trying, but if you want to handle integer overflow, just include limits.h
at your source code and write down this line inside your for loop.
if (i >= INT_MAX) break;
this will make you able to check your variable does not become greater than can it fit in integer.
Upvotes: 0
Reputation: 20402
As you noticed yourself, signed integer overflow is undefined. The compiler decides to reason about your program assuming that you're smart enough to never cause undefined behavior. So it can conclude that since i
is initialized to a number larger than 2 and only gets incremented, it will never be lower or equal to 2, which means that i > 2
can never be false. This in turn means that the loop will never terminate and can be optimized into an infinite loop.
Upvotes: 0