Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Question about GCC Optimizer and why this code always returns 42?

I ran into an error recently where a variable was initialized inside a switch statement. I started playing around with this more and realize I don't know the first thing about what GCC is trying to do in some of these optimizations.

Given this code:

int main(int argc, char** argv) {
       switch (argc) {
               case 1000: return 42;
               int y = 24;
               default: return y;
       }
       return argc;
}

The generated code always returns 42. What is going on? Why does the int y = 24 muck everything up?

$ gcc -Wall -Werror -O2 -c test.c
$ objdump -drwCS -Mintel test.o

testo.o:     file format elf64-x86-64

Disassembly of section .text.startup:

0000000000000000 <main>:
   0:   b8 2a 00 00 00          mov    eax,0x2a
   5:   c3                      ret
like image 414
Justin Van Horne Avatar asked Feb 08 '19 06:02

Justin Van Horne


People also ask

Why should code optimization be controlled by a compile time flag?

Turning on optimization flags makes the compiler attempt to improve the performance and/or code size at the expense of compilation time and possibly the ability to debug the program.

Does GCC optimize by default?

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

How do I know if my compiler is not optimized?

Compiler specific pragma gcc provides pragma GCC as a way to control temporarily the compiler behavior. By using pragma GCC optimize("O0") , the optimization level can be set to zero, which means absolutely no optimize for gcc.

What optimization does GCC do?

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.


2 Answers

int main(int argc, char** argv) {
    switch (argc) {
        case 1000: return 42;
        int y = 24;
        default: return y;
    }
    return argc;
}

To explain this a bit more, a switch doesn't exactly do a linear progression. The logic equivalent to this would be:

"If argc is 1000, return 42. Otherwise return y"

The int y = 24; is never used since it's never reached, the compiler can optimize this out, and since there's UB in the case of a default, it might as well return 42.

To fix this and behave the way I suspect you intend, you just need to declare y outside of the switch statement.

int main(int argc, char** argv) {
    int y = 24;
    switch (argc) {
        case 1000: return 42;
        default: return y;
    }
    return argc;
}
like image 195
Daniel Avatar answered Oct 18 '22 21:10

Daniel


Cases in a switch are to be regarded as labels. If we translate your code to the equivalent goto-spaghetti, it might be easier to understand:

int main(int argc, char** argv) 
{
  if(argc == 1000) 
    goto label_1000;
  else 
    goto label_default;

  label_1000: return 42;

  int y = 24;

  label_default: return y;

  return argc;
}

The goto label_default jumps past the label initialization of y and so it doesn't necessarily get executed. The same thing happens in your switch.

Best practice when declaring variables inside switches is therefore to always use a compound statement per case:

case 1000:
{
  int y = 24;
  break;
}

Apart from preventing spaghetti bugs, this also reduces the scope of the variable to the specific case.

like image 34
Lundin Avatar answered Oct 18 '22 21:10

Lundin