switch...case和if...else的根本区别switch...case会生成一个跳表来表示实际case分支的地址,这个跳转表的索引号等于switch变量的值。因此,switch...case不需要像if...else一样遍历条件分支直到条件命中,而只需要访问索引号对应的表项即可达到定位分支的目的。具体来说,switch...case会生成一个跳表,其大小(条目数)为最大case常量+1。程序首先判断switch变量是否大于最大case常量,如果是则跳转到处理的默认分支;否则获取索引号为switch变量大小的跳表项地址(即跳表起始地址+表项大小*索引号),然后程序跳转到该地址执行,分支跳转完成。第一步是编写一个演示程序:foo.c#includestaticintfoo_ifelse(charc){if(c=='0'||c=='1'){c+=1;}elseif(c=='a'||c=='b'){c+=2;}elseif(c=='A'||c=='B'){c+=3;}else{c+=4;}return(c);}staticintfoo_switch(charc){switch(c){case'1':case'0':c+=1;break;case'b':case'a':c+=2;break;case'B':case'A':c+=3;break;default:c+=4;break;}return(c);}intmain(intargc,char**argv){intm1=foo_ifelse('0');intm2=foo_ifelse('1');intn1=foo_switch('a');intn2=foo_switch('b');(void)printf("%c%c%c%c\n",m1,m2,n1,n2);return(0);}第二步,在Ubuntu上用gcc编译$gcc-g-ofoofoo.c第三步,用gdb反汇编二进制文件foo(使用intel语法)o反汇编foo_ifelse()(gdb)setdisassembly-flavorintel(gdb)disas/mfoo_ifelseDumpofassemblercodeforfunctionfoo_ifelse:4{0x0804841d<+0>:pushebp0x0804841e<+1>:movebp,esp0x08048420<+3>:subesp,0x40x08048420<+3>:subesp0+0<8484>:subesp0+0x484subesp,0x40x080<+p>eb+moveTR0x8]0x08048426<+9>:movBYTEPTR[ebp-0x4],al5if(c=='0'||c=='1'){0x08048429<+12>:cmpBYTEPTR[ebp-0x4],0x300x0804842d<+16>:je0x80484350x0804842f<+18>:cmpBYTEPTR[ebp-0x4],0x310x08048433<+22>:jne0x80484416c+=1;0x08048435<+24>:movzxeax,BYTEPTR[0x08404]<+28>:addeax,0x10x0804843c<+31>:movBYTEPTR[ebp-0x4],al0x0804843f<+34>:jmp0x804847b7}elseif(c=='a'||c=='b'){0x08048441<+36>:cmpBYTEPTR[ebp-0x4],0x610x08048445<+40>:je0x804844d0x08048447<+42>:cmpBYTEPTR[ebp-0x4],0x620x0804844b<+4foneifse56>4:60>8c+=2;0x0804844d<+48>:movzxeax,BYTEPTR[ebp-0x4]0x08048451<+52>:addeax,0x20x08048454<+55>:movBYTEPTR[ebp-0x4],al0x08048457<+58>:jmp0x8048454<+55>:jmp0x8048457<+58>:jmp0x804847b+94>9}elseif(c=='A'||c=='B'){0x08048459<+60>:cmpBYTEPTR[ebp-0x4],0x410x0804845d<+64>:je0x80484650x0804845f<+66>:cmpBYTEPTR[ebp-0x4],0x420x08048463<+70>:jne0x804847110c+=3;0x08048465<+72>:movzxeax,BYTEPTR[ebp-0x4]0x08048469<+76>:addeax,0x30x0804846c<+79>:movBYTEPTR[ebp-0x4],al0x0804846f<+82>:jmp0x804847b11}else{12c+=4;0x08048471<+84>:movzxeax,BYTEPTR[ebp-0x4]0x08048475<+88>:addeax,0x40x08048478<+91>:movBYTEPTR[ebp-0x4],al13}1415return(c);0x0804847b<+94>:movsxeax,BYTEPTR[ebp-0x4]16}0x0804847f<+98>:leave0x08048480<+99>:retEndofassemblerdump.(gdb)o反汇编foo_ifelse()(gdb)setdisassembly-flavorintel(gdb)disas/mfoo_ifelseDumpofassemblercodeseforfunction:4{_ifelse0x0804841d<+0>:pushebp0x0804841e<+1>:movebp,esp0x08048420<+3>:subesp,0x40x08048423<+6>:moveax,DWORDPTR[ebp+0x8]0x08048426<+9>:movBYTEPTR[ebp-0x4],al5if(c=='0'||c=='1'){0x08048429<+12>:cmpBYTEPTR[ebp-0x4],0x300x0804842d<+16>:je0x80484350x0804842f<+18>:cmpBYTEPTR[ebp-0x4],0x310x08048433<+22>:jne0x80484416c+=1;0x08048435<+24>:movzxeax,BYTEPTR[ebp-0x4]0x08048439<+28>:addeax,0x10x0804843c<+31>:movBYTEPTR[ebp-0x4],al0x0804843f<+34>:jmp0x804_847b7}elseif(c=='a'||c=='b'){0x08048441<+36>:cmpBYTEPTR[ebp-0x4],0x610x08048445<+40>:je0x804844d0x08048447<+42>:cmpBYTEPTR[ebp-0x4],0x620x0804844b<+46>:jne0x80484598c+=2;0x0804844d<+48>:movzxeax,BYTEPTR[ebp-0x4]0x08048451<+52>:addeax,0x480x04855>:movBYTEPTR[ebp-0x4],al0x08048457<+58>:jmp0x804847b9}elseif(c=='A'||c=='B'){0x08048459<+60>:cmpBYTEPTR[ebp-0x4],0x410x0804845d<+64>:je0x80484650x0804845f<+66>:cmpBYTEPTR[ebp-0x4],0x420x08048463<+70>:jne0x804847120x65<07+3>:movzxeax,BYTEPTR[ebp-0x4]0x08048469<+76>:addeax,0x30x0804846c<+79>:movBYTEPTR[ebp-0x4],al0x0804846f<+82>:jmp0x804847b11}else{12c+=4;0x08048471<+84>:movzxeax,BYTEPTR[ebp-0x4]0x08048475<+88>:addeax,0x40x08048478<+91>:movBYTEPTR[ebp-0x4],al13}1415return(c);0x0804847b<+94>:movsxeax,BYTEPTR[ebp-0x4]16}0x0804847f<+98>:leave0x08048480<+99>:retEndofassemblerdump.(gdb)o反汇编foo_switch()(gdb)setdisassembly-flavorintel(gdb)disas/mfoo_switchDumpofassemblercodeforfunctionfoo_switch:20{0x08048481<+0>:pushebp0x08048482<+1>:movebp,esp0x08048484<+3>:subesp,0x40x08048487<+6>:moveax,ebp+0xDP8[]0x0804848a<+9>:movBYTEPTR[ebp-0x4],al21switch(c){0x0804848d<+12>:movsxeax,BYTEPTR[ebp-0x4]0x08048491<+16>:subeax,0x300x08048494<+19>:cmpeax,0x32804908<+22>:ja0x80484c60x08048499<+24>:moveax,DWORDPTR[eax*4+0x80485f0]0x080484a0<+31>:jmpeax22case'1':23case'0':c+=1;break;0x080484a2<+33>:movzxeax,BYTEPTR[ebp-0x4]0x080484a6<+37>:addeax,0x10x080484a9<+40>:movBYTEPTR[ebp-0x4],al0x080484ac<+43>:jmp0x80484d124case'b':25case'a':c+=2;break;0x080484ae<+45>:movzxeax,BYTEPTR[ebp-0x4]0x080484b2<+49>:addeax,0x20x080484b5<+52>:movBYTEPTR[ebp-0x4],al0x080484b8<+55>:jmp0x80484d126case'B':27case'A':c+=3;break;0x080484ba<+57>:movzxeax,BYTEPTR[ebp-0x4]0x080484be<+61>:addeax,0x30x080484c1<+64>:movBYTEPTR[ebp-0x4],al0x080484c4<+67>:jmp0x80484d128default:c+=4;break;0x080484c6<+69>:movzxeax,BYTEPTR[ebp-0x4]0x080484ca<+73>:addeax,0x40x080484cd<+76>:movBYTEPTR4[ebp-0x4]]],al0x080484d0<+79>:nop29}3031return(c);0x080484d1<+80>:movsxeax,BYTEPTR[ebp-0x4]32}0x080484d5<+84>:leave0x080484d6<+85>:retEndofassemblerdump.(gdb)分析:在foo_ifelse()中,使用的方法是顺序比较,如果满足条件,则执行相应的代码,否则跳转到下一个分支再比较;在foo_switch()中,下面的汇编代码比较有意思,..21switch(c){0x0804848d<+12>:movsxeax,BYTEPTR[ebp-0x4]0x08048491<+16>:subeax,0x300x08048494<+19>:cmpeax,0x320x08048497<+22>:ja0x80484c8:69>moveax,DWORDPTR[eax*4+0x80485f0]0x080484a0<+31>:jmpeax..注:第17行jmpeax的意思是当c的值不同时,什么机制保证第17行能跳转到正确的地方去开始执行?第16行:eax=[eax*4+0x80485f0]我想通了,从地址0x80485f0开始,对应内存的内容也回答了刚才的问题执行第16行后,当c为'1'或'0'时,eax的值应为0x080484a2;当c为'b'或'a'时,eax的值应为0x080484ae;当c为'B'或'A'时,eax的值应为0x080484ba;通过gdb查看对应的内存,确实如此!>>>ord('1')-0x30>>>ord('0')-0x30(gdb)x/2wx0*4+0x80485f00x80485f0:0x080484a20x080484a2>>>ord('b')-0x30>>>ord('a')-0x30(gdb)x/2wx49*4+0x80485f00x80486b4:0x080484ae0x080484ae>>>ord('B')-0x30>>>ord('A')-0x30(gdb)x/2wx17*4+0x80485f00x8048634:0x080484ba0x080484ba那么,我们可以大胆猜测,虽然c的值不同,但是重定向的IP确实是准确的。它必须在编译阶段设置。是真的吗?接下来分析对应的二进制文件foo,第四步,使用objdump查看foo,$objdump-Dfoo>/tmp/x$vim/tmp/x509Disassemblyofsection.rodata:...51880485f0:a2840408a2mov%al,0xa208048451980485f5:840408test%al,(%eax,%ecx,1)...5348048630:c6840408ba8404movb$0x8,0x484ba08(%esp,%eax,1)5358048637:085368048638:ba840408c6mov$0xc6080484,%edx...56680486b0:c6840408ae8404movb$0x8,0x484ae08(%esp,%eax,1)56780486b7:0856880486b8:aescas%es:(%edi),%al56980486b9:840408test%al,(%eax,%ecx,1)...8wordsstoredataddress0x80485f0节日是幸运的是,0x080484a2,0x080484a2(注意:按小端方式读取)在地址0x80486b4,存储的8字节正好是0x080484ae,而0x080484ae在地址0x8048634,存储的8字节正好是0x080484ba,0x080484ba正好是正如预期的那样。跳转IP的值在编译时保存在.rodata(只读数据区)中。一旦foo开始运行,对应的内存地址就填入了正确的待跳转地址,接下来就是根据c计算出IP存储对应的内存起始地址X,从中取出待跳转地址X,直接跳转就好了。160x08048499<+24>:moveax,DWORDPTR[eax*4+0x80485f0]170x080484a0<+31>:jmpeax到此为止,我们已经搞清楚为什么switch...case...语句是相对于if...elseif...else...执行效率高的根本原因。简而言之,在编译时,会创建一个地图并将其存储在.rodata区域中。运行时直接根据输入(c的值)查表,找到对应IP后直接跳转。(省略了cmp,jmp->cmp,jmp->cmp,jmp...的冗长计算过程。)总结:switch...case...执行效率高,是典型的以空间换时间。也就是说,(用算法的术语来说)以增加空间复杂度为代价降低了时间复杂度。【责任编辑:庞桂玉电话:(010)68476606】