Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does GCC -O2 and -O3 optimization break this program?

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 :)

like image 393
BojanV03 Avatar asked Jan 08 '23 11:01

BojanV03


2 Answers

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.

like image 135
M.M Avatar answered Jan 21 '23 04:01

M.M


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.

like image 40
Jim Wood Avatar answered Jan 21 '23 03:01

Jim Wood