程序举例:
#include <stdio.h>
int main()
{
int i=0;
int j=0;
switch(i)
{
case 1:
j+=1;
break;
case 2:
j+=2;
break;
case 3:
j+=3;
break;
case 4:
j+=4;
break;
case 5:
j+=5;
case 6:
j+=70;
break;
default:
j+=5;
break;
}
return 0;
}
通过指令gcc -S daima.c -o daima.s
得到反汇编代码
1)先看看main函数部分:
注意:由于i>6和i<0时是default,把i看做无符号数与6比较,如果比6大,就跳转到.L2(结束)
main:
.LFB0:
pushl %ebp
movl %esp, %ebp
subl $16, %esp
movl $0, -4(%ebp) //i=0
movl $0, -8(%ebp) //j=0
cmpl $6, -4(%ebp)
ja .L2
//i>6跳转到地址.L2(这里把i看作无符号数,所以i为负数时也会跳转)
movl -4(%ebp), %eax
sall $2, %eax //%eax=4*i
addl $.L9, %eax //%eax=$.L9+4*i(.L9是个地址值)
movl (%eax), %eax
jmp *%eax
2)跳转表
.L9是个地址值,跳转表显示从地址.L9开始的一段连续地址空间里面存的内容(每四个字节就会存一个对应的地址值)
.L9://跳转表 .L9是个地址值
.long .L2 //i=0 l9~l9+3这4个字节内存放l2的地址
.long .L3 //i=1 l9+4~l9+7这4个字节存放l3的地址,后面同理
.long .L4
.long .L5
.long .L6
.long .L7
.long .L8
.text
3)分支代码段
.L3://case1 j+1
addl $1, -8(%ebp)
jmp .L10
.L4://case2 j+2
addl $2, -8(%ebp)
jmp .L10
.L5://case3 j+3
addl $3, -8(%ebp)
jmp .L10
.L6://case4 j+4
addl $4, -8(%ebp)
jmp .L10
.L7://case5 j+5
addl $5, -8(%ebp)
.L8://case6 j+70
addl $70, -8(%ebp)
jmp .L10
.L2://default
addl $5, -8(%ebp)
nop
.L10:
movl $0, %eax
leave
ret
将分支条件调整为case 6,case 2,case 5,case 3,case 4,case 1(即交换一下分支条件顺序),观察跳转表的变化情况
.L8:
addl $70, -8(%ebp)//j+70
jmp .L10
.L4:
addl $2, -8(%ebp)//j+2
jmp .L10
.L7:
addl $5, -8(%ebp)//j+5
jmp .L10
.L5:
addl $3, -8(%ebp)//j+3
jmp .L10
.L6:
addl $4, -8(%ebp)//j+4
.L3:
addl $1, -8(%ebp)//j+1
jmp .L10
.L2:
addl $5, -8(%ebp)//j+5
nop
.L10:
movl $0, %eax
leave
结果分析:
可以看到除了分支代码段不一样其余部分保持不变。
(1)分支代码段只做了顺序的调整,按照了case 6,case 2,case 5,case 3,case 4,case 1的顺序排布。
(2)但是很有趣的是,虽然代码的顺序变化了,但是.L和操作仍然是一一对应的!和以前一摸一样!这也说明gcc编译内部不是按照代码的绝对顺序来对应地址的,而是有一定的规则,比如说这里是按照j+1,j+2,j+3……来设置的。这说明了一个问题,switch对执行代码段的顺序做了优化。
注意:如果case语句数量过少,不会有跳转表,汇编代码直接用cmp jmp等跳转指令表示
部分内容参考:https://blog.csdn.net/weixin_44307065/article/details/105478981