switch语句可以根据一个整数索引值进行多重分支。处理具有多种可能结果的测试时,这种语句特别有用。它们不仅提高了代码的可读性,而且使用跳转表这个数据结构使用实现更加高效。跳转表是一个数组,表项i是一个代码段的地址,这个代码段实现当switch索引值等于i时程序应该执行的动作。程序代码用于索引值来执行一个跳转表内的数组引用,确定跳转指令的目标。和使用一组很长的
if-else相比,使用跳转表的优点是执行switch语句的时间与switch的case数量无关。GCC根据switch语句中case的数量和case中值的稀少程序来翻译开关语句。当case数据比较多(例如4个以上),并且值的范围跨度比较小时,就会使用跳转表。
1. 编写switch.c程序如下:
int func(int x) { int ret = -1; switch(x) { case 1: ret = 2; break; case 2: ret = 1; break; case 4: case 5: ret = 6; break; case 6: ret = 3; break; default: ret = 0; break; } return ret; } |
2. 编译为汇编程序
unix> gcc -S switch.c
3.查看汇编程序switch.s主要内容如下:
初步分析,x值存储在8(%ebp)位置,ret值存储在-4(%ebp)中。图中,.L7位置申请了一系列存储用于存储.L2~.L6的信息,每个元素为long型,占用4字节的空间(注意.L2处的代码是default的内容)。假如把.L7看成是一个数据,根据跳转表的原理,我们分析,若x处于1~4之间,只需执行.L7[x]处的程序即可,即跳转地址为.L7+4*x。
按照上面思路,我们忽略程序过程调用的一些压栈操作和分配内存等操作从第15行看。第15、16行代码作用是比较x与5的大小,如果5<x,那么跳转到.L2(即switch.c中的default操作),否则把x的值加载到eax寄存器。行18把x的值扩大为原来的4倍,然后ADD .L8。计算得到要switch要跳转的地址,最后执行jmp *%eax(含义见
汇编控制类指令(2)—— 跳转指令).
通过上面例子,我们知道C语言把跳转表声明为一个多元素的数组,第个元素都是一个指向代码的指针。跳转表对于重复情况处理就是简单地对重复项填入相同的标号(case 4、5),而对于缺失情况填入default的标号(switch内容为3时)。