case 如何在 switch 语句中被评估 (C)

how cases get evaluated in switch statements (C)

我正在学习 C,我已经熟悉基本的编程概念 我对 switch 语句有疑问 对于以下代码中的 ex

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);
    }

输出为 16 21

意思是label 1处的case先被执行,然后因为没有break,label 2,3,4也被执行了,问题是如果label 1被执行了那么i的值更新为5 , 其他情况是先检查条件(如果 i =1 或 5 )然后执行还是不检查就执行任何东西?

在 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);
} 

所以当 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*/

将控制权交给case标签0:后依次执行,i等于16.

你可以想象循环的第一次迭代中的 switch 语句是这样的

goto Label0;

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

标签只是标签。他们不执行任何代码。评估 switch 语句中的表达式后

switch(i)

控制权交给对应的标签语句,如果没有跳转语句,则依次执行后面的所有语句。

然后在 for 循环的第三个表达式中 i 递增并等于 17.

因此在 for 循环的下一次迭代中,控制立即传递给标签 default,并且 i 变为等于 21

这是一个很好的问题,实际上揭示了 C 和 C++ 中 switch 语句的内部结构,有时会与级联 if-else 语句混淆。

switch语句在C/C++中的作用如下:

  • (1) 首先计算 switch 语句
  • 中作为条件出现的表达式
  • (2) 将结果存入栈或使用通用寄存器
  • (3) 使用该结果,它尝试通过使用跳转-table(当可以构建时)以尽可能少的比较跳转到相应的 case 语句。

由于 (1) 和 (2),您创建的 switch 未按您预期的方式运行,并且在 [= 执行期间不会重新计算初始表达式18=] 语句。

与级联 if-else 语句相反,您的 case 语句本质上是按顺序编译的指令块,由 (3) 中提到的跳转 table 引用。一旦执行到 case 语句,如果没有遇到 break,它将自动级联到下一个 case 语句。 break 实际上指示编译器 跳过 switch 语句并停止执行 case 语句。

查看您的 switch 语句的这个注释反汇编,只是为了更好地掌握引擎盖下发生的事情:

   0x56555585 <+56>:    mov    -0x10(%ebp),%eax       ;<--- store "i" (the switch condition) into EAX
   0x56555588 <+59>:    cmp    [=10=]x1,%eax              ;<--- check "case 1"
   0x5655558b <+62>:    je     0x5655559a <main+77>   ;<--- jump if equal to "case 1"
   0x5655558d <+64>:    cmp    [=10=]x5,%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   [=10=]x5,-0x10(%ebp)       ;<--- case 0
   0x5655559a <+77>:    addl   [=10=]x2,-0x10(%ebp)       ;<--- case 1
   0x5655559e <+81>:    addl   [=10=]x5,-0x10(%ebp)       ;<--- case 5
   0x565555a2 <+85>:    addl   [=10=]x4,-0x10(%ebp)       ;<--- default

注意:这是使用 -m32 -O0 gcc 选项构建的,以使用更易于阅读的 32 位代码,并禁用优化。

你可以清楚地看到,在跳转(到任何 case 语句)之后,没有对 i (-0x10(%ebp)) 的进一步重新评估。另外,当案例执行时,如果没有使用break,它会自动级联到下一个。

现在,您可能会问自己为什么会出现这种奇怪的行为,答案在 (3):跳转到具有最少比较的相应案例语句可能.

C/C++ 中的 switch 语句在 case 语句的数量真正增加时,尤其是当用于 case语句不变。

例如,假设我们有一个包含 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-else 语句,您将获得 100 次比较,但是此 switch 仅使用几条指令就可以获得相同的结果,顺序为:

  • 首先:编译器将在跳转中索引所有case语句table
  • second:它将评估 switch 中的条件并存储结果(即:获取 i
  • third:根据结果计算跳转table中对应的index(即:i1,第一个case语句,结果在索引 99)
  • 第四,直接跳转到对应的case,不做任何操作

如果您的个案值的分布为 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;
}

您的编译器也应该检测到这种传播,并且在减去第一个 case 值(1)后将除以 2 以获得相同的跳转索引 table。

switch 语句的这种复杂的内部工作机制使其成为 C/C++ 中非常强大的工具,当您想根据只能在 [= 处计算的值分支代码时120=]-时间,并且该值属于均匀分布的集合,或者至少属于均匀分布的值组。

case 值分布不均时,switch 的效率会降低,并且它的性能开始与我们使用级联 if-else 时类似。