在程序中我们经常用到switch case,它的用法就不再用多说了。计算switch中的值然后比较,跳转到相应的分支。很多人说在编译是时通过转化成if。。else来实现的。但实际使用时尤其在调试代码时,每次的case跳转都是直接跳转到匹配值的。这样就与用if。。。else有些矛盾。那么switch。。case究竟如何实现的那?实际试验一下:
//switch_test1.c
#include
#include
int main()
{
int i;
i=1;
switch(i)
{
case 1:
printf("%d/n",i);
break;
case 2:
printf("%d/n",i);
break;
case 3:
printf("%d/n",i);
break;
case 4:
printf("%d/n",i);
break;
deflaut:
break;
}
return 1;
}
对test1.c用Gcc进行到汇编程序的编译
Gcc -S test1.c –o test.s
得到文件内容如下:
.file "switch-test1.c"
.def ___main; .scl 2; .type 32; .endef
.section .rdata,"dr"
LC0:
.ascii "%d/12/0"
.text
.globl _main
.def _main; .scl 2; .type 32; .endef
_main:
pushl %ebp
movl %esp, %ebp
subl $24, %esp
andl $-16, %esp
movl $0, %eax
addl $15, %eax
addl $15, %eax
shrl $4, %eax
sall $4, %eax
movl %eax, -12(%ebp)
movl -12(%ebp), %eax
call __alloca
call ___main
movl $1, -4(%ebp)
movl -4(%ebp), %eax
movl %eax, -8(%ebp)
cmpl $2, -8(%ebp)
je L4
cmpl $2, -8(%ebp)
jg L8
cmpl $1, -8(%ebp)
je L3
jmp L2
L8:
cmpl $3, -8(%ebp)
je L5
cmpl $4, -8(%ebp)
je L6
jmp L2
L3:
movl -4(%ebp), %eax
movl %eax, 4(%esp)
movl $LC0, (%esp)
call _printf
jmp L2
L4:
movl -4(%ebp), %eax
movl %eax, 4(%esp)
movl $LC0, (%esp)
call _printf
jmp L2
L5:
movl -4(%ebp), %eax
movl %eax, 4(%esp)
movl $LC0, (%esp)
call _printf
jmp L2
L6:
movl -4(%ebp), %eax
movl %eax, 4(%esp)
movl $LC0, (%esp)
call _printf
L7:
L2:
movl $1, %eax
leave
ret
.def _printf; .scl 3; .type 32; .endef
Linux的汇编格式与Intel的不同,但是基本指令还是部分相似的,在对操作数取用时会加%,而且源和宿与intel相反。具体的内容不讨论。L3到L6程序片段内容基本相似,是调用printf函数实现打印。L8及之前可以看出通过大量的CMPL比较指令进行比较i与立即数比较,相等则实现跳转。查看部分程序:
cmpl $2, -8(%ebp)#低八位与2比较
je L4#相等则跳转L4,打印i
cmpl $2, -8(%ebp) #低八位与2比较
jg L8//大于则跳转 L8
cmpl $1, -8(%ebp) #低八位与1比较
je L3#相等则跳转L3
jmp L2#跳转到L2,返回1结束程序
L8:
cmpl $3, -8(%ebp) #低八位与3比较
je L5#相等则跳转L6,打印i
cmpl $4, -8(%ebp) #低八位与4比较
je L6#相等则跳转L6,打印i
jmp L2
经过注释可以看出,所有的case想都经过比较来实现是跳转的。再看一下if-else的实现会如何,
//if_else.c
#include
#include
int main()
{
int i;
i=1;
if(i==1)
{
printf("%d/n",i);
}
else if(i==2)
{
printf("%d/n",i);
}
else if(i==3)
{
printf("%d/n",i);
}
else if(i==4)
{
printf("%d/n",i);
}
else
{
}
return 1;
}
同样编译至汇编,查看生成的汇编代码:
.file "if_else.c"
.def ___main; .scl 2; .type 32; .endef
.section .rdata,"dr"
LC0:
.ascii "%d/12/0"
.text
.globl _main
.def _main; .scl 2; .type 32; .endef
_main:
pushl %ebp
movl %esp, %ebp
subl $24, %esp
andl $-16, %esp
movl $0, %eax
addl $15, %eax
addl $15, %eax
shrl $4, %eax
sall $4, %eax
movl %eax, -8(%ebp)
movl -8(%ebp), %eax
call __alloca
call ___main
movl $1, -4(%ebp)
cmpl $1, -4(%ebp)
jne L2
movl -4(%ebp), %eax
movl %eax, 4(%esp)
movl $LC0, (%esp)
call _printf
jmp L3
L2:
cmpl $2, -4(%ebp)
jne L4
movl -4(%ebp), %eax
movl %eax, 4(%esp)
movl $LC0, (%esp)
call _printf
jmp L3
L4:
cmpl $3, -4(%ebp)
jne L6
movl -4(%ebp), %eax
movl %eax, 4(%esp)
movl $LC0, (%esp)
call _printf
jmp L3
L6:
cmpl $4, -4(%ebp)
jne L3
movl -4(%ebp), %eax
movl %eax, 4(%esp)
movl $LC0, (%esp)
call _printf
L3:
movl $1, %eax
leave
ret
.def _printf; .scl 3; .type 32; .endef
简单阅读就可以发现,两者的程序有点差距,但是基本实现逻辑都是相似,比较i与立即数(c程序中的常数1,2,3,4),相等时打印,然后跳转到返回结束程序。好像真的是如大家所说,switch-case程序最终实现会使用if-else的逻辑实现。但是为什么,在调试会有直接跳转的问题那,实际程序与例程的区别应该就在,case数上了。实际是case分支高达20多个,那么提升case分支再次尝试一下。
//switch_test2.c
#include
#include
int main()
{
int i;
i=1;
switch(i)
{
case 1:
printf("%d/n",i);
break;
case 2:
printf("%d/n",i);
break;
case 3:
printf("%d/n",i);
break;
case 4:
printf("%d/n",i);
break;
case 5:
printf("%d/n",i);
break;
case 6:
printf("%d/n",i);
break;
case 7:
printf("%d/n",i);
break;
case 8:
printf("%d/n",i);
break;
deflaut:
break;
}
return 1;
}
汇编代码:
.file "switch_test2.c"
.def ___main; .scl 2; .type 32; .endef
.section .rdata,"dr"
LC0:
.ascii "%d/12/0"
.text
.globl _main
.def _main; .scl 2; .type 32; .endef
_main:
pushl %ebp
movl %esp, %ebp
subl $24, %esp
andl $-16, %esp
movl $0, %eax
addl $15, %eax
addl $15, %eax
shrl $4, %eax
sall $4, %eax
movl %eax, -8(%ebp)
movl -8(%ebp), %eax
call __alloca
call ___main
movl $1, -4(%ebp)
cmpl $8, -4(%ebp)
ja L2
movl -4(%ebp), %eax
sall $2, %eax
movl L12(%eax), %eax
jmp *%eax
.section .rdata,"dr"
.align 4
L12:
.long L2
.long L3
.long L4
.long L5
.long L6
.long L7
.long L8
.long L9
.long L10
.text
L3:
movl -4(%ebp), %eax
movl %eax, 4(%esp)
movl $LC0, (%esp)
call _printf
jmp L2
L4:
movl -4(%ebp), %eax
movl %eax, 4(%esp)
movl $LC0, (%esp)
call _printf
jmp L2
L5:
movl -4(%ebp), %eax
movl %eax, 4(%esp)
movl $LC0, (%esp)
call _printf
jmp L2
L6:
movl -4(%ebp), %eax
movl %eax, 4(%esp)
movl $LC0, (%esp)
call _printf
jmp L2
L7:
movl -4(%ebp), %eax
movl %eax, 4(%esp)
movl $LC0, (%esp)
call _printf
jmp L2
L8:
movl -4(%ebp), %eax
movl %eax, 4(%esp)
movl $LC0, (%esp)
call _printf
jmp L2
L9:
movl -4(%ebp), %eax
movl %eax, 4(%esp)
movl $LC0, (%esp)
call _printf
jmp L2
L10:
movl -4(%ebp), %eax
movl %eax, 4(%esp)
movl $LC0, (%esp)
call _printf
L11:
L2:
movl $1, %eax
leave
ret
.def _printf; .scl 3; .type 32; .endef
简单的分析可以看出L12作为一个表项,实现了到L2到L10的跳转。那么可以看出,switch-case在实现时当case项比较多时,会通过生成查询表来提高程序的效率,空间换时间。
在此基础上,对case的顺序打乱,只要case的值比较规律(数据差相同),都是汇编成查询表,但是各个case值之间非常离散时,即无规律可言时,是不能能生成查询表的,只能使用if-else的方式。这种情况下只用将最有可能的值放在第一个比较判断的位置才能极大提高程序效率。