概述
我于昨晚去世,走时心如止水。我于今早重生,来时心怀暖阳。敬你岁月无波澜,祝我余生不悲欢!
散文集 - 《我在人间凑数的日子》
一、参考资料
ARM M3/M4汇编指令TBB TBH实现复杂表格跳转
C语言switch语句的汇编语言实现
C语言汇编代码分析(switch case)
本文是在看大佬们的文章后自己总结的,感谢大佬们的文章分享。
二、具体分析
我们知道 C 语言的 switch 关键字可以让一些情况下的分支判断变得更简洁,可以避免大量使用 if-else if-else
不断判断的情况,让代码看起来更简洁。
那么问题来了,switch
和 if
语句间到底在底层实现上有什么区别呢?
直接说结论,其实答案还不是唯一,和编译器实现还有具体的 switch
有关,下面我们分别讨论。
以下以 ARM 汇编为例,别的架构的编译器可能会有不同的结果,不过应该大同小异。
2.1、case 数量情况少且 case 值比较连续的看情况
C代码:
switch (mode)
{
case 0:
count = 1;
break;
case 1:
count = 2;
break;
case 2:
count = 4;
break;
case 3:
count = 4;
break;
default :
break;
}
看汇编实现:
51: switch (mode)
52: {
53: case 0:
0x0800018A B135 CBZ r5,0x0800019A
0x0800018C 2D01 CMP r5,#0x01
0x0800018E D006 BEQ 0x0800019E
0x08000190 2D02 CMP r5,#0x02
0x08000192 D006 BEQ 0x080001A2
0x08000194 2D03 CMP r5,#0x03
0x08000196 D108 BNE 0x080001AA
0x08000198 E005 B 0x080001A6
54: count = 1;
0x0800019A 2401 MOVS r4,#0x01
55: break;
56:
57: case 1:
0x0800019C E006 B 0x080001AC
58: count = 2;
0x0800019E 2402 MOVS r4,#0x02
59: break;
60:
61: case 2:
0x080001A0 E004 B 0x080001AC
62: count = 4;
0x080001A2 2404 MOVS r4,#0x04
63: break;
64:
65: case 3:
0x080001A4 E002 B 0x080001AC
66: count = 4;
0x080001A6 2404 MOVS r4,#0x04
67: break;
68:
69: default :
0x080001A8 E000 B 0x080001AC
70: break;
71: }
我们看到使用了非常多的 CMP
指令,其实在这种情况下和使用多个 if-else if
一样。
2.2、case 数量情况多且 case 值比较连续的看情况
C 代码:
switch (mode)
{
case 0:
count = 1;
break;
case 1:
count = 2;
break;
case 2:
count = 4;
break;
case 3:
count = 4;
break;
case 4:
count = 1;
break;
case 6:
count = 2;
break;
case 8:
count = 4;
break;
case 10:
count = 4;
break;
default :
break;
}
我们来看下汇编:
51: switch (mode)
52: {
53: case 0:
0x0800018A 2D0B CMP r5,#0x0B
0x0800018C D217 BCS 0x080001BE
0x0800018E E8DFF005 TBB [pc,r5]
0x08000192 0806 DCW 0x0806
0x08000194 0C0A DCW 0x0C0A
0x08000196 160E DCW 0x160E
0x08000198 1610 DCW 0x1610
0x0800019A 1612 DCW 0x1612
0x0800019C 0014 DCW 0x0014
54: count = 1;
0x0800019E 2401 MOVS r4,#0x01
55: break;
56:
57: case 1:
0x080001A0 E00E B 0x080001C0
58: count = 2;
0x080001A2 2402 MOVS r4,#0x02
59: break;
60:
61: case 2:
0x080001A4 E00C B 0x080001C0
62: count = 4;
0x080001A6 2404 MOVS r4,#0x04
63: break;
64:
65: case 3:
0x080001A8 E00A B 0x080001C0
66: count = 4;
0x080001AA 2404 MOVS r4,#0x04
67: break;
68:
69: case 4:
0x080001AC E008 B 0x080001C0
70: count = 1;
0x080001AE 2401 MOVS r4,#0x01
71: break;
72:
73: case 6:
0x080001B0 E006 B 0x080001C0
74: count = 2;
0x080001B2 2402 MOVS r4,#0x02
75: break;
76:
77: case 8:
0x080001B4 E004 B 0x080001C0
78: count = 4;
0x080001B6 2404 MOVS r4,#0x04
79: break;
80:
81: case 10:
0x080001B8 E002 B 0x080001C0
82: count = 4;
0x080001BA 2404 MOVS r4,#0x04
83: break;
84: default :
0x080001BC E000 B 0x080001C0
85: break;
86: }
87: }
我们可以看到 TBB
(ARM M3/M4汇编指令TBB TBH实现复杂表格跳转)这个汇编指令, 这个指令用于 switch 跳转,其实现方式有些类似数组,我们将程序中的 case
值作为数组的索引,就可以立即得到目标值。
这种方式是采用空间换时间的方式,在占用更多内存的情况下,我们可以快速匹配到要执行的语句。
值得注意的是这里只要求 case
比较连续即可,即使这里举例用的是 100-104
。这点也可以类比数组,将其映射到 0-4
即可。
2.3、 case 值跨度比较大的看情况
看 C 代码,只是将上文的 case 10
改为 case 50
:
switch (mode)
{
case 0:
count = 1;
break;
case 1:
count = 2;
break;
case 2:
count = 4;
break;
case 3:
count = 4;
break;
case 4:
count = 1;
break;
case 6:
count = 2;
break;
case 8:
count = 4;
break;
case 50:
count = 4;
break;
default :
break;
}
对应汇编:
51: switch (mode)
52: {
53: case 0:
0x0800018A 2D04 CMP r5,#0x04
0x0800018C D017 BEQ 0x080001BE
0x0800018E DC07 BGT 0x080001A0
0x08000190 B16D CBZ r5,0x080001AE
0x08000192 2D01 CMP r5,#0x01
0x08000194 D00D BEQ 0x080001B2
0x08000196 2D02 CMP r5,#0x02
0x08000198 D00D BEQ 0x080001B6
0x0800019A 2D03 CMP r5,#0x03
0x0800019C D117 BNE 0x080001CE
0x0800019E E00C B 0x080001BA
0x080001A0 2D06 CMP r5,#0x06
0x080001A2 D00E BEQ 0x080001C2
0x080001A4 2D08 CMP r5,#0x08
0x080001A6 D00E BEQ 0x080001C6
0x080001A8 2D32 CMP r5,#0x32
0x080001AA D110 BNE 0x080001CE
0x080001AC E00D B 0x080001CA
54: count = 1;
0x080001AE 2401 MOVS r4,#0x01
55: break;
56:
57: case 1:
0x080001B0 E00E B 0x080001D0
58: count = 2;
0x080001B2 2402 MOVS r4,#0x02
59: break;
60:
61: case 2:
0x080001B4 E00C B 0x080001D0
62: count = 4;
0x080001B6 2404 MOVS r4,#0x04
63: break;
64:
65: case 3:
0x080001B8 E00A B 0x080001D0
66: count = 4;
0x080001BA 2404 MOVS r4,#0x04
67: break;
68:
69: case 4:
0x080001BC E008 B 0x080001D0
70: count = 1;
0x080001BE 2401 MOVS r4,#0x01
71: break;
72:
73: case 6:
0x080001C0 E006 B 0x080001D0
74: count = 2;
0x080001C2 2402 MOVS r4,#0x02
75: break;
76:
77: case 8:
0x080001C4 E004 B 0x080001D0
78: count = 4;
0x080001C6 2404 MOVS r4,#0x04
79: break;
80:
81: case 50:
0x080001C8 E002 B 0x080001D0
82: count = 4;
0x080001CA 2404 MOVS r4,#0x04
83: break;
84:
85: default :
0x080001CC E000 B 0x080001D0
86: break;
87: }
我们看到这种情况下,编译器采用了和第一种举例的情况的实现方式,也就是使用类似 if-else if
的方式。
至于为什么会这样,是编译器看到 case
的值相差太大,如果用第二种那种跳转表的方式,占用的空间会大大增大(类似数组,即使你只用了索引 0
和 26
,起码你也得定义数组 arr[27]
,大大浪费别的25个数组值),聪明编译器又岂会不懂这个道理,所以选择了这种方式,就是在时间和空间中选择了节省大量空间。
总结
我们看到,switch
语句的具体实现其实是会根据不同的情况会有不同的实现。编译器真是个聪明的东西呢。
以前还注意到 switch
的实现竟然还有那么多的门道,要学的东西真多,自己动手去探究也更能够理解更深刻。
最后
以上就是粗暴宝马为你收集整理的C语言的 switch 语句的底层汇编具体实现(以ARM汇编为例讲解)一、参考资料二、具体分析总结的全部内容,希望文章能够帮你解决C语言的 switch 语句的底层汇编具体实现(以ARM汇编为例讲解)一、参考资料二、具体分析总结所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复