简体   繁体   English

如何在 switch 语句中评估案例 (C)

[英]how cases get evaluated in switch statements (C)

I'm corrently learning C, and I'm allready familiar with basic programming concepts I have a question about switch statement for ex in the following code我正在学习C,我已经熟悉基本的编程概念我有一个关于以下代码中ex的switch语句的问题

for(int i =0 ; i<20; i++){

        switch(i){
        case 0: i+=5; /*label 1*/
        case 1: i+=2; /*label 2*/
        case 5: i+=5; /*label 3*/
        default : i+=4; /*label 4*/
        }
        printf("%d\t",i);
    }

the output is 16 21输出是 16 21

that means that case at label 1 is executed first, then since there is no break, label 2, 3 and 4 are also executed, the question is that if label 1 is executed then value of i is updated to 5, does other cases check the condition first (if i =1 or 5 ) then execute or it just execute anything without checking?也就是说先执行label 1的case,然后因为没有break,label 2, 3, 4也被执行,问题是如果label 1被执行了,那么i的值更新为5,其他case会检查首先条件(如果 i =1 或 5 )然后执行或它只是执行任何东西而不检查?

In the labeled parts of the switch statement there are no break statements在 switch 语句的标记部分中没有 break 语句

for(int i =0 ; i<20; i++){

    switch(i){
    case 0: i+=5; /*label 1*/
    case 1: i+=2; /*label 2*/
    case 5: i+=5; /*label 3*/
    default : i+=4; /*label 4*/
    }
    printf("%d\t",i);
} 

So when i is equal to 0 (in the first iteration of the loop) then all these statements因此,当i等于0 (在循环的第一次迭代中)时,所有这些语句

    case 0: i+=5; /*label 1*/
    case 1: i+=2; /*label 2*/
    case 5: i+=5; /*label 3*/
    default : i+=4; /*label 4*/

are executed sequentially after passing the control to the case label 0: and i becomes equal to 16 .在将控制权传递给 case 标签0:后依次执行,并且i变得等于16

You can imagine the switch statement in the first iteration of the loop the following way您可以通过以下方式想象循环第一次迭代中的 switch 语句

goto Label0;

Label0: i+=5; /*label 1*/
Label1: i+=2; /*label 2*/
Label5: i+=5; /*label 3*/
Labeldefault : i+=4; /*label 4*/

Labels are just labels.标签只是标签。 They do not execute any code.它们不执行任何代码。 After evaluating the expression in the switch statement在对 switch 语句中的表达式求值之后

switch(i)

the control is passed to the corresponding labeled statement and all the following statements are executed sequentially if there is no jump statement.控制权传递给相应的标记语句,如果没有跳转语句,则按顺序执行以下所有语句。

Then in the third expression of the for loop i is incremented and becomes equal to 17 .然后在 for 循环的第三个表达式中, i递增并等于17

So in the next iteration of the for loop the control at once is passed to the label default and i becomes equal to 21 .所以在 for 循环的下一次迭代中,控制立即传递给标签default并且i变得等于21

It's a very good question, and actually reveals the internals of the switch statement in C and C++, which can sometimes be confused with cascading if-else statements.这是一个很好的问题,实际上揭示了 C 和 C++ 中switch语句的内部结构,有时会与级联if-else语句混淆。

The switch statement in C/C++ works as follows: C/C++ 中的switch语句的工作原理如下:

  • (1) first it evaluates the expression presented as a condition in the switch statement (1) 首先它评估作为switch语句中的条件的表达式
  • (2) stores the result on the stack or using a general-purpose register (2) 将结果存入堆栈或使用通用寄存器
  • (3) using that result it attempts to jump to the corresponding case statement with the minimum comparisons possible by using a jump-table (when one can be built). (3) 使用该结果,它尝试使用跳转表(当可以构建时)以尽可能少的比较跳转到相应的 case 语句。

It is because of (1) and (2) that the switch you created is not behaving the way you may expect, and it doesn't reevaluate the initial expression during the execution of the case statements.正是因为 (1) 和 (2),您创建的switch没有按照您预期的方式运行,并且它不会在case语句的执行过程中重新计算初始表达式。

In contrast with cascading if-else statements, your case statements are essentially blocks of instructions compiled in sequential order, referenced by a jump table as mentioned at (3).与级联if-else语句相比,您的case语句本质上是按顺序编译的指令块,由 (3) 中提到的跳转表引用。 Once the execution reaches a case statement, it will automatically cascade over the next case statements if break is not encountered.一旦执行到一个case语句,如果没有遇到break ,它将自动级联下一个case语句。 The break actually instructs the compiler to jump over the switch statement and stop executing the case statements. break实际上指示编译器跳过switch 语句并停止执行case语句。

Check out this commented disassembly of your switch statement, just to have a better grip of what's happening under the hood:看看你的 switch 语句的这个评论反汇编,只是为了更好地掌握引擎盖下发生的事情:

   0x56555585 <+56>:    mov    -0x10(%ebp),%eax       ;<--- store "i" (the switch condition) into EAX
   0x56555588 <+59>:    cmp    $0x1,%eax              ;<--- check "case 1"
   0x5655558b <+62>:    je     0x5655559a <main+77>   ;<--- jump if equal to "case 1"
   0x5655558d <+64>:    cmp    $0x5,%eax              ;<--- check "case 5"
   0x56555590 <+67>:    je     0x5655559e <main+81>   ;<--- jump if equal to "case 5" 
   0x56555592 <+69>:    test   %eax,%eax              ;<--- check "case 0"
   0x56555594 <+71>:    jne    0x565555a2 <main+85>   ;<--- jump if not equal to "default"
   0x56555596 <+73>:    addl   $0x5,-0x10(%ebp)       ;<--- case 0
   0x5655559a <+77>:    addl   $0x2,-0x10(%ebp)       ;<--- case 1
   0x5655559e <+81>:    addl   $0x5,-0x10(%ebp)       ;<--- case 5
   0x565555a2 <+85>:    addl   $0x4,-0x10(%ebp)       ;<--- default

Note: this is built with -m32 -O0 gcc options to use 32bit code which is much easier to read, and disable optimizations.注意:这是使用-m32 -O0 gcc 选项构建的,以使用更易于阅读的 32 位代码,并禁用优化。

You can clearly see that after the jump is made (to any case statement) there is no further reevaluation of i ( -0x10(%ebp) ).您可以清楚地看到,在进行跳转后(对于任何 case 语句),不再对i ( -0x10(%ebp) ) 进行重新评估。 Also, when the case is executed, it automatically cascades to the next one if no break is used.此外,当案例被执行时,如果没有使用break ,它会自动级联到下一个案例。

Now, you may ask yourself why this odd behavior and the answer is at (3): to jump to the corresponding case statement with the minimum comparisons possible .现在,您可能会问自己为什么会出现这种奇怪的行为,答案是 (3):以尽可能少的比较跳转到相应的 case 语句

The switch statements in C/C++ show their true strength when the number of case statements really scales up and especially when the spread between the values used for the case statements is constant.case语句的数量真正增加时,尤其是当用于case语句的值之间的分布是恒定的时,C/C++ 中的switch语句显示了它们的真正优势。

For example, let's assume we have a large switch statement with 100 case values, with a constant spread of 1 between case values and that the switch expression ( i ) evaluates to 100 (last case in the switch ):例如,假设我们有一个包含 100 个case值的大型switch语句,在 case 值之间具有1的常数分布,并且 switch 表达式 ( i ) 的计算结果为100switch最后一个case ):

switch (i) {
 case 1: /*code for case 1*/ break;
 case 2: /*code for case 2*/ break;
 [...]
 case 99: /*code for case 99*/ break;
 case 100: /*code for case 100*/ break;
}

If you used cascading if-else statements you would get 100 comparisons, but this switch can obtain the same result using just a couple of instructions, in this order:如果您使用级联if-else语句,您将获得 100 次比较,但此switch只需使用几条指令即可获得相同的结果,按以下顺序:

  • first: the compiler will index all the case statements in a jump table第一:编译器会索引一个跳转表中的所有case语句
  • second: it will evaluate the condition in the switch and store the result (ie: fetch i )第二:它将评估switch的条件并存储结果(即: fetch i
  • third: it calculates the corresponding index in the jump table based on the result (ie: decrement i by 1 , the first case statement, results in index 99)第三:根据结果计算跳转表中对应的索引(即:将i1 ,第一个case语句,结果索引为99)
  • fourth, it jumps directly to the corresponding case without any further operation第四,直接跳转到对应的case不做任何进一步的操作

The same will apply if your case values have a spread of 2 :如果您的案例值的传播为2则同样适用:

switch (i) {
 case 1: /*code for case 1*/ break;
 case 3: /*code for case 3*/ break;
 [...]
 case 99: /*code for case 99*/ break;
 case 101: /*code for case 101*/ break;
}

Your compiler should detect this spread too and after subtracting the first case value (which is 1 ) will divide by 2 to obtain the same index for the jump table.您的编译器也应该检测到这种传播,在减去第一个 case 值(即1 )后,将除以2以获得跳转表的相同索引。

This complicated inner-workings of the switch statement makes it a very powerful tool in C/C++ when you want to branch your code based on a value you can only evaluate at run-time, and when that value belongs to a set that is evenly spread, or at least, groups of values with an even spread.当您想根据只能在运行时计算的值对代码进行分支时,并且当该值属于均匀集合时, switch语句的这种复杂的内部工作方式使其成为 C/C++ 中非常强大的工具传播,或者至少是传播均匀的值组。

When the case values don't have an even spread, the switch becomes less efficient and it starts to perform similarly to if we have used cascading if-else instead.case值没有均匀分布时, switch效率会降低,并且它开始执行类似于我们使用级联if-else

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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