简体   繁体   中英

C++, switch case, and the compiler

From what I've read from various answers to similar questions, switch cases are compiled differently under some circumstances.

I have a few scenarios in mind but I'm not sure how they will compile.

Scenario 1

A switch case which takes an enum value. The cases range from 0 - 99 and are ordered.

Scenario 2

A switch case which takes an enum value. The cases range from lets say, 0 - 30, 50 - 80, 100 - 150. Unordered. Will this compile differently from the scenario above?


Basically I'd like to know how the switch cases in the scenarios will be compiled and if any differences exist between the two scenarios. Thanks!

edit: I should have mentioned that one of my biggest concerns is how many checks it would take to match the case. If statements are linear so for scenario one, if it was an if-else-if it would take at most 100 checks, unless I am mistaken. But how is this handled with switch-cases? What kind of optimizations does the compiler make?

There is no way to tell in general. The C++ standard makes hardly any assumption about the code which is generated. This means the compiler is free to almost (yes, there are exceptions) re-order you code, as long as the semantics stays the same. Optimisation was often called "the black magic of compiler construction" when I took courses in that topic.

Having this in mind, your compiler could spew out the same or different code. It may spit out the same code for some optimisation levels and completely different code for others. It may completely depend on the code you are using in the case blocks themselves. If you have the same code at two points, it might just optimize it to use that code once, or it might not.

The only way to know what will happen is to look at the output of a compiler with certain fixed flags and see what happens. And once you found out what happens, do not rely on it, it might change in the next version of the compiler.

If you are programming always keep to the guarantees the language standard gives you and never assume anything beyond that.

In theory, for a full switch a compiler will generate a jump table, that is, an array of pointers to labels in which to index directly (provided that the value fits the range).

For a less full version, it's up to the compiler to balance jump table and branch statements. It will probably depend on the settings too: whether you maximize speed with -O3 or minimize size with -Oz is certain to influence some edge cases.

Finally the switch only has one case, it will probably be represented as a branch instead.

The presence of break or fallthrough does not influence things much. Assembly is defined in terms of labels, and both break and fallthrough are naturally represented, so it should not influence whether branches or jump tables are used.


Demo (using Try out LLVM website)

enum Enum {
  Zero,
  One,
  Two,
  Three,
  Four,
  Five,
  Six,
  Seven,
  Eight,
  Nine
};

char const* full(enum Enum e) {
  switch(e) {
  case Zero: return "Zero";
  case One: return "One";
  case Two: return "Two";
  case Three: return "Three";
  case Four: return "Four";
  case Five: return "Five";
  case Six: return "Six";
  case Seven: return "Seven";
  case Eight: return "Eight";
  case Nine: return "Nine";
  }

  return "Uh ?";
}

char const* sparse(enum Enum e) {
  switch(e) {
  default: return "Not handled";
  case Zero: return "Zero";
  case One: return "One";
  case Two: return "Two";
  case Seven: return "Seven";
  case Eight: return "Eight";
  case Nine: return "Nine";
  }
}

char const* null(enum Enum e) {
  switch(e) {
  default: return "Not Zero";
  case Zero: return "Zero";
  }
}

Function full is compiled to:

    .text
    .globl  full
    .align  16, 0x90
    .type   full,@function
full:                                   # @full
.Ltmp0:
    .cfi_startproc
# BB#0:
    cmpl    $9, %edi
    ja  .LBB0_11
# BB#1:
    movl    %edi, %ecx
    movl    $.L.str, %eax
    jmpq    *.LJTI0_0(,%rcx,8)
.LBB0_2:
    movl    $.L.str1, %eax
    ret
.LBB0_3:
    movl    $.L.str2, %eax
    ret
.LBB0_4:
    movl    $.L.str3, %eax
    ret
.LBB0_5:
    movl    $.L.str4, %eax
    ret
.LBB0_6:
    movl    $.L.str5, %eax
    ret
.LBB0_7:
    movl    $.L.str6, %eax
    ret
.LBB0_8:
    movl    $.L.str7, %eax
    ret
.LBB0_9:
    movl    $.L.str8, %eax
    ret
.LBB0_10:
    movl    $.L.str9, %eax
    ret
.LBB0_11:
    movl    $.L.str10, %eax
.LBB0_12:
    ret
.Ltmp1:
    .size   full, .Ltmp1-full
.Ltmp2:
    .cfi_endproc
.Leh_func_end0:
    .section    .rodata,"a",@progbits
    .align  8
.LJTI0_0:
    .quad   .LBB0_12
    .quad   .LBB0_2
    .quad   .LBB0_3
    .quad   .LBB0_4
    .quad   .LBB0_5
    .quad   .LBB0_6
    .quad   .LBB0_7
    .quad   .LBB0_8
    .quad   .LBB0_9
    .quad   .LBB0_10

And function sparse to:

    .text
    .globl  sparse
    .align  16, 0x90
    .type   sparse,@function
sparse:                                 # @sparse
.Ltmp3:
    .cfi_startproc
# BB#0:
    movl    $.L.str11, %eax
    cmpl    $9, %edi
    ja  .LBB1_8
# BB#1:
    movl    %edi, %ecx
    jmpq    *.LJTI1_0(,%rcx,8)
.LBB1_2:
    movl    $.L.str, %eax
    ret
.LBB1_3:
    movl    $.L.str1, %eax
    ret
.LBB1_4:
    movl    $.L.str2, %eax
    ret
.LBB1_5:
    movl    $.L.str7, %eax
    ret
.LBB1_6:
    movl    $.L.str8, %eax
    ret
.LBB1_7:
    movl    $.L.str9, %eax
.LBB1_8:
    ret
.Ltmp4:
    .size   sparse, .Ltmp4-sparse
.Ltmp5:
    .cfi_endproc
.Leh_func_end1:
    .section    .rodata,"a",@progbits
    .align  8
.LJTI1_0:
    .quad   .LBB1_2
    .quad   .LBB1_3
    .quad   .LBB1_4
    .quad   .LBB1_8
    .quad   .LBB1_8
    .quad   .LBB1_8
    .quad   .LBB1_8
    .quad   .LBB1_5
    .quad   .LBB1_6
    .quad   .LBB1_7

And finally the function null :

    .text
    .globl  null
    .align  16, 0x90
    .type   null,@function
null:                                   # @null
.Ltmp0:
    .cfi_startproc
# BB#0:
    movl    $.L.str1, %ecx
    testl   %edi, %edi
    movl    $.L.str, %eax
    cmoveq  %rcx, %rax
    ret
.Ltmp1:
    .size   null, .Ltmp1-null
.Ltmp2:
    .cfi_endproc
.Leh_func_end0:

Most compilers will compile these differently. But it completely depends on the compiler and the architecture.

Seems like you just need to take a look into disassembler. Switch is indeed very interesting ) Take a look with and without optimization. Have a fun =)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM