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
) 的计算结果为 100
(switch
中的最后一个 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(即:
i
减1
,第一个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
时类似。
我正在学习 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
) 的计算结果为 100
(switch
中的最后一个 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(即:
i
减1
,第一个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
时类似。