switch...case与if...else的根本区别 switch...case会生成一个跳转表来指示实际的case分支的地址,而这个跳转表的索引号与switch变量的值是相等的。从而,switch...case不用像if...else那样遍历条件分支直到命中条件,而只需访问对应索引号的表项从而到达定位分支的目的。 具体地说,switch...case会生成一份大小(表项数)为最大case常量+1的跳表,程序首先判断switch变量是否大于最大case 常量,若大于,则跳到default分支处理;否则取得索引号为switch变量大小的跳表项的地址(即跳表的起始地址+表项大小*索引号),程序接着跳到此地址执行,到此完成了分支的跳转。 第一步,写一个demo程序:foo.c#include <stdio.h>* ~/ T1 _& e) V( t- _- V, d static int foo_ifelse(char c)+ i' f( a+ u3 \. v6 S) Z {' u- }" }; r L* c/ ^ if (c == '0' || c == '1') { c += 1;" L( |# c. e+ c8 j, b! J& _ } else if (c == 'a' || c == 'b') { c += 2;# H, {6 f8 i9 X+ Y } else if (c == 'A' || c == 'B') { c += 3;2 R2 \7 x* F1 r0 d* r/ F4 ^ } else { c += 4; }3 _4 x9 }( K/ W4 g' L3 U, v 3 k& {* C. |5 }5 J* Y return (c);2 P0 b1 F8 N, A# s } static int4 g: z+ o1 e+ c2 K. } foo_switch(char c)3 `% \4 @- R ~8 h- H6 L { switch (c) {/ g5 d1 ~0 p# L" F8 B, K1 L$ | case '1': case '0': c += 1; break;9 E- K+ r! Y8 s case 'b':1 @3 h5 A4 u3 K6 d case 'a': c += 2; break; case 'B': case 'A': c += 3; break; default: c += 4; break; c( B) V9 y- x' M0 _+ X }1 Q |1 z5 b) z' V0 }: j 8 d8 j5 p% G i0 R' [" h+ K- \ return (c); } int% J u+ i# f3 \+ F, [! f; L2 M9 P" x main(int argc, char **argv)9 h. N! Q6 g" E- y% x0 H { int m1 = foo_ifelse('0');0 V' I* }: M0 N/ e8 N. B int m2 = foo_ifelse('1'); int n1 = foo_switch('a');: q" a5 S) x4 ^3 U6 k! l# X int n2 = foo_switch('b');, H1 k6 P! ^1 r. R (void) printf("%c %c %c %c\n", m1, m2, n1, n2); return (0); } 第二步,在Ubuntu上使用gcc编译 $ gcc -g -o foo foo.c 第三步,使用gdb对二进制文件foo反汇编 (使用intel语法)o 反汇编foo_ifelse()(gdb) set disassembly-flavor intel) h9 i/ k. w- L1 n) K( {6 D (gdb) disas /m foo_ifelse Dump of assembler code for function foo_ifelse:% E$ f: c0 x9 k$ f* } 4 {4 y9 ]3 z3 J5 }& V# x 0x0804841d <+0>: push ebp& L+ g U9 M m 0x0804841e <+1>: mov ebp,esp( a: p8 H: b/ G4 q 0x08048420 <+3>: sub esp,0x4- r& i8 v* p ^( ` 0x08048423 <+6>: mov eax,DWORD PTR [ebp+0x8] 0x08048426 <+9>: mov BYTE PTR [ebp-0x4],al' g- ?# m9 }2 z. ] 5 if (c == '0' || c == '1') { z! T! x8 ^2 V: p& j# N; E( | 0x08048429 <+12>: cmp BYTE PTR [ebp-0x4],0x30+ b! c# o; A c* o 0x0804842d <+16>: je 0x8048435 <foo_ifelse+24> 0x0804842f <+18>: cmp BYTE PTR [ebp-0x4],0x31. e8 P6 a3 r( b9 \+ ?) D 0x08048433 <+22>: jne 0x8048441 <foo_ifelse+36> 6 c += 1;& v6 ~" f/ s. N5 r& ]/ J 0x08048435 <+24>: movzx eax,BYTE PTR [ebp-0x4]5 x' G( w3 r x/ w5 y 0x08048439 <+28>: add eax,0x11 U! L% ? x+ F: q D7 j4 K 0x0804843c <+31>: mov BYTE PTR [ebp-0x4],al1 P7 w% g3 G/ q 0x0804843f <+34>: jmp 0x804847b <foo_ifelse+94>) [ U8 }- y$ l% s5 G 2 u* K- p& n/ ?6 D* V- G 7 } else if (c == 'a' || c == 'b') { 0x08048441 <+36>: cmp BYTE PTR [ebp-0x4],0x61 0x08048445 <+40>: je 0x804844d <foo_ifelse+48>7 X0 M3 U6 b. |' P7 a Y1 Z* m 0x08048447 <+42>: cmp BYTE PTR [ebp-0x4],0x629 N' }! u# W- B% I5 R+ x 0x0804844b <+46>: jne 0x8048459 <foo_ifelse+60>6 \6 }1 f, a( x5 M; S, a 4 _+ R$ G! u! x- M$ c 8 c += 2; 0x0804844d <+48>: movzx eax,BYTE PTR [ebp-0x4] 0x08048451 <+52>: add eax,0x2 0x08048454 <+55>: mov BYTE PTR [ebp-0x4],al% g1 h8 s" G. S. U. K 0x08048457 <+58>: jmp 0x804847b <foo_ifelse+94>) b& D' V7 R; p/ X5 t+ l: [; s 9 } else if (c == 'A' || c == 'B') {5 w' J6 `/ p' r5 g5 [3 p$ K* L; T0 Q 0x08048459 <+60>: cmp BYTE PTR [ebp-0x4],0x416 W9 y; ]0 c- _7 r 0x0804845d <+64>: je 0x8048465 <foo_ifelse+72> 0x0804845f <+66>: cmp BYTE PTR [ebp-0x4],0x42; d8 b' C2 y: k- r- e* L- | 0x08048463 <+70>: jne 0x8048471 <foo_ifelse+84> . U* P6 b" a6 _# P2 Y3 Q7 h' k+ b 10 c += 3; 0x08048465 <+72>: movzx eax,BYTE PTR [ebp-0x4] 0x08048469 <+76>: add eax,0x36 n w) E* |9 `, T; g! M+ I/ g 0x0804846c <+79>: mov BYTE PTR [ebp-0x4],al 0x0804846f <+82>: jmp 0x804847b <foo_ifelse+94># c- }+ q- E M' L3 @+ u 6 @! n# C2 T) P* q) ], W! y1 ~ 11 } else {$ V1 v' v' K) L: L 12 c += 4;7 q/ ?/ U J7 ^ 0x08048471 <+84>: movzx eax,BYTE PTR [ebp-0x4]' G }. L' q& v! G 0x08048475 <+88>: add eax,0x46 y( Y- s6 s% M, { 0x08048478 <+91>: mov BYTE PTR [ebp-0x4],al5 L/ R& i/ X8 w1 J# [1 Y. g 1 d3 w/ p' F& b7 q* ^0 i 13 } 14 15 return (c);. T# l1 B* |6 L9 E% U9 f; u 0x0804847b <+94>: movsx eax,BYTE PTR [ebp-0x4] % M& u6 F+ I% z, j$ B 16 } 0x0804847f <+98>: leave$ K$ @" R& p3 `6 Y3 T 0x08048480 <+99>: ret End of assembler dump. (gdb)o 反汇编foo_ifelse()* ^6 c( q( Z l5 | (gdb) set disassembly-flavor intel9 K# c3 u5 h) m% W (gdb) disas /m foo_ifelse Dump of assembler code for function foo_ifelse:$ c$ d6 F+ m% J; x7 h 4 { 0x0804841d <+0>: push ebp7 U( x( L4 L& f% W" F+ K1 j% [ 0x0804841e <+1>: mov ebp,esp) d5 ^$ E c; r9 k, F 0x08048420 <+3>: sub esp,0x4 0x08048423 <+6>: mov eax,DWORD PTR [ebp+0x8] 0x08048426 <+9>: mov BYTE PTR [ebp-0x4],al' B' N/ t9 X5 Q+ x t 5 if (c == '0' || c == '1') {# ?- g+ S9 P! j1 |" N 0x08048429 <+12>: cmp BYTE PTR [ebp-0x4],0x304 E, y8 u( A8 p, Z8 E$ V/ x 0x0804842d <+16>: je 0x8048435 <foo_ifelse+24> 0x0804842f <+18>: cmp BYTE PTR [ebp-0x4],0x31 0x08048433 <+22>: jne 0x8048441 <foo_ifelse+36>$ Q# `6 X9 k) p8 \5 ` 6 c += 1;7 L+ B' F$ U( ?/ K 0x08048435 <+24>: movzx eax,BYTE PTR [ebp-0x4]' u5 V# X& i* v 0x08048439 <+28>: add eax,0x1 0x0804843c <+31>: mov BYTE PTR [ebp-0x4],al 0x0804843f <+34>: jmp 0x804847b <foo_ifelse+94>' J1 X- J$ _0 l9 q: E; L 7 } else if (c == 'a' || c == 'b') { W, w) Z2 \) B; E% V S5 ^ 0x08048441 <+36>: cmp BYTE PTR [ebp-0x4],0x61 0x08048445 <+40>: je 0x804844d <foo_ifelse+48>/ D" [/ l, ?7 y9 M5 y 0x08048447 <+42>: cmp BYTE PTR [ebp-0x4],0x62 V4 g$ O1 ] I8 p! K1 k/ @( H 0x0804844b <+46>: jne 0x8048459 <foo_ifelse+60>/ z2 X% t5 x |% I4 d! m, W / U! V& S5 D$ ~3 E/ N 8 c += 2; 0x0804844d <+48>: movzx eax,BYTE PTR [ebp-0x4] 0x08048451 <+52>: add eax,0x2 0x08048454 <+55>: mov BYTE PTR [ebp-0x4],al 0x08048457 <+58>: jmp 0x804847b <foo_ifelse+94>3 c) m/ w$ C/ Q8 N. a 5 J1 j2 \! B. u$ Y! | 9 } else if (c == 'A' || c == 'B') {5 a. w, V/ z7 _ k8 c/ f+ X% _5 Y# I 0x08048459 <+60>: cmp BYTE PTR [ebp-0x4],0x41 0x0804845d <+64>: je 0x8048465 <foo_ifelse+72>& z# R4 G4 |9 J i3 e, j% K 0x0804845f <+66>: cmp BYTE PTR [ebp-0x4],0x42# Y& ], ?6 o: y4 ^3 w 0x08048463 <+70>: jne 0x8048471 <foo_ifelse+84> 10 c += 3;+ R, j2 S; z& t% ^5 U9 v 0x08048465 <+72>: movzx eax,BYTE PTR [ebp-0x4] 0x08048469 <+76>: add eax,0x3 0x0804846c <+79>: mov BYTE PTR [ebp-0x4],al1 w2 m/ s: S5 c; V 0x0804846f <+82>: jmp 0x804847b <foo_ifelse+94>9 D' s2 I- i# b6 v: ^5 C l% n( u# \ g& G& K' i+ ]" L. J 11 } else {( r1 o2 b( T$ m: ] 12 c += 4; 0x08048471 <+84>: movzx eax,BYTE PTR [ebp-0x4] 0x08048475 <+88>: add eax,0x4 0x08048478 <+91>: mov BYTE PTR [ebp-0x4],al 2 P4 X; t+ O1 M0 d 13 } 14 15 return (c); 0x0804847b <+94>: movsx eax,BYTE PTR [ebp-0x4] 16 }7 s- F, {0 Q) E6 {! ^0 Y8 B 0x0804847f <+98>: leave- v' Y2 K. N$ Z% C J 0x08048480 <+99>: ret + k v# P4 b9 ^" u# _1 F5 k3 c/ J# ? End of assembler dump.7 j6 O! E+ B: N* A$ J/ b" }+ t (gdb) o 反汇编foo_switch() (gdb) set disassembly-flavor intel. z; |, [9 G( P2 k& M(gdb) disas /m foo_switch Dump of assembler code for function foo_switch:1 e) d6 ?% C$ Z( N% w7 A 20 {7 l% Y. U7 _" [; }# [% r 0x08048481 <+0>: push ebp 0x08048482 <+1>: mov ebp,esp( W, u9 t9 |9 f8 f2 o& Y' _* m) u. e 0x08048484 <+3>: sub esp,0x4 0x08048487 <+6>: mov eax,DWORD PTR [ebp+0x8]# I9 S) T7 {) v$ \* i 0x0804848a <+9>: mov BYTE PTR [ebp-0x4],al' m3 \" @3 `1 h2 j+ {, X 21 switch (c) {1 u: l/ b6 _- \ 0x0804848d <+12>: movsx eax,BYTE PTR [ebp-0x4] 0x08048491 <+16>: sub eax,0x308 r Z" g% ?( r1 p& A" t 0x08048494 <+19>: cmp eax,0x32 X; \, Q! |3 q6 s) K9 Y7 t. `8 ` 0x08048497 <+22>: ja 0x80484c6 <foo_switch+69>! s0 A# h+ ^$ I! U. [" Z 0x08048499 <+24>: mov eax,DWORD PTR [eax*4+0x80485f0] 0x080484a0 <+31>: jmp eax" J' E! r3 s6 O+ ^: `+ v8 [' u- w 22 case '1':9 Q) u+ @" h% b, y7 | 23 case '0': c += 1; break;5 I" u, h7 U. |, {" Y) m 0x080484a2 <+33>: movzx eax,BYTE PTR [ebp-0x4] 0x080484a6 <+37>: add eax,0x1' E; X6 y5 u2 | Y 0x080484a9 <+40>: mov BYTE PTR [ebp-0x4],al 0x080484ac <+43>: jmp 0x80484d1 <foo_switch+80>7 u. J) d% Q& w# g- N' O: `, f2 ` $ W% U) I3 }( o: Z9 O; _* O 24 case 'b':! ], ~. b1 x s G; ^ 25 case 'a': c += 2; break;$ `, i0 W' O) h P1 X6 o 0x080484ae <+45>: movzx eax,BYTE PTR [ebp-0x4] 0x080484b2 <+49>: add eax,0x2) _8 W |$ S" i" v' H7 U1 Q+ w 0x080484b5 <+52>: mov BYTE PTR [ebp-0x4],al 0x080484b8 <+55>: jmp 0x80484d1 <foo_switch+80> 26 case 'B':- U& @# b2 T j) n& h% | 27 case 'A': c += 3; break;: U5 I a7 T0 U 0x080484ba <+57>: movzx eax,BYTE PTR [ebp-0x4] 0x080484be <+61>: add eax,0x3 0x080484c1 <+64>: mov BYTE PTR [ebp-0x4],al 0x080484c4 <+67>: jmp 0x80484d1 <foo_switch+80>0 a! ]8 ^1 F) z+ ^8 Q4 r) Y & L+ w9 V) r4 z) @, z, W- J 28 default: c += 4; break;" d2 z+ J0 J# x0 S3 W 0x080484c6 <+69>: movzx eax,BYTE PTR [ebp-0x4] 0x080484ca <+73>: add eax,0x4 0x080484cd <+76>: mov BYTE PTR [ebp-0x4],al, I( l8 y8 _0 c5 s4 a, Z9 z 0x080484d0 <+79>: nop . w8 C6 W) Z+ p1 C7 V, o. b. f+ a 29 }- F; w: p6 c3 e' T D 30; s! h/ L2 H8 X( F$ M9 } 31 return (c); 0x080484d1 <+80>: movsx eax,BYTE PTR [ebp-0x4] 8 _4 X7 G' ]) m2 o+ y7 ~" h. D6 G 32 }- e" j8 ~0 e7 }6 y 0x080484d5 <+84>: leave/ J- T1 X" c5 n% R; ? 0x080484d6 <+85>: ret7 t+ B9 j" f6 L' J# y3 H7 i 9 t A2 U: G* Y6 A+ a1 Y End of assembler dump.2 F. g3 ]$ S R (gdb) 分析:
21 switch (c) { 0x0804848d <+12>: movsx eax,BYTE PTR [ebp-0x4]( O5 c% J% S" `: V0 g 0x08048491 <+16>: sub eax,0x30) V% G4 {; P5 z 0x08048494 <+19>: cmp eax,0x32# Y6 T& [6 \$ Z3 W 0x08048497 <+22>: ja 0x80484c6 <foo_switch+69># S+ L* N9 b, F) [ 0x08048499 <+24>: mov eax,DWORD PTR [eax*4+0x80485f0]6 n: T: y) W& A ]; |; m* D 0x080484a0 <+31>: jmp eax .. 注意: 第17行 jmp eax 也就是说,当c的取值不同,是什么机制保证第17行能跳转到正确的位置开始执行呢? 第16行: eax = [eax * 4 + 0x80485f0] 搞清楚了从地址0x80485f0开始,对应的内存里面的内容也就回答了刚才的问题。 执行完第16行后,
通过gdb查看对应的内存,确实如此! >>> ord('1') - 0x30>>> ord('0') - 0x30 (gdb) x /2wx 0*4+0x80485f0+ D7 X4 I8 b9 k: B7 U0 U" z 0x80485f0: 0x080484a2 0x080484a2; M& x& e/ F. B1 R7 |! q7 d + i+ r, ~$ V ?9 V. [& x- @ >>> ord('b') - 0x30 >>> ord('a') - 0x30 (gdb) x /2wx 49*4+0x80485f0 0x80486b4: 0x080484ae 0x080484ae9 M9 v9 H7 F3 `# R7 R 4 W2 \2 W$ x! W" }' z >>> ord('B') - 0x30# P( |+ H) G+ _- _ >>> ord('A') - 0x30 (gdb) x /2wx 17*4+0x80485f0$ V$ m% d+ m' j5 D 0x8048634: 0x080484ba 0x080484ba( Q: [+ d% |# } 那么,我们可以大胆的猜测,虽然c的取值不同但是跳转的IP确实是精准无误的,一定是编译阶段就被设定好了,果真如此吗?接下来分析一下对应的二进制文件foo, 第四步,使用objdump查看foo,$ objdump -D foo > /tmp/x9 e1 E3 W. u' d# ^" B1 Z) J$ vim /tmp/x 509 Disassembly of section .rodata: ... 518 80485f0: a2 84 04 08 a2 mov %al,0xa2080484 519 80485f5: 84 04 08 test %al,(%eax,%ecx,1)( m% v, h% q5 T. v5 t ... 534 8048630: c6 84 04 08 ba 84 04 movb $0x8,0x484ba08(%esp,%eax,1) 535 8048637: 089 N' T' @, Y- q X& i 536 8048638: ba 84 04 08 c6 mov $0xc6080484,%edx5 K( Q+ b/ Z( F% Q- { ... 566 80486b0: c6 84 04 08 ae 84 04 movb $0x8,0x484ae08(%esp,%eax,1)0 |( |# q3 k$ n2 {" B 567 80486b7: 08' v& b( {& T7 Y: H( d1 W 568 80486b8: ae scas %es %edi),%al569 80486b9: 84 04 08 test %al,(%eax,%ecx,1) ...7 @9 S# _! s) U/ O 在0x80485f0地址,存的8个字节正好是0x080484a2, 0x080484a2 (注意:按照小端的方式阅读) 在0x80486b4地址,存的8个字节正好是0x080484ae, 0x080484ae 在0x8048634地址,存的8个字节正好是0x080484ba,0x080484ba 果然不出所料,要跳转的IP的值正是在编译的时候存入了.rodata(只读数据区)。一旦foo开始运行,对应的内存地址就填写上了正确的待跳转地址,接下来只不过是根据c的取值计算出对应的IP存放的内存起始地址X,从X中取出待跳转的地址,直接跳转就好。 16 0x08048499 <+24>: mov eax,DWORD PTR [eax*4+0x80485f0]0 Y. C% M7 j s$ k2 @( L+ K0 ]17 0x080484a0 <+31>: jmp eax! l% W) D) Y" p: u, y 到此为止,我们已经搞清楚了为什么switch...case...语句相对于if...else if...else...来说执行效率要高的根本原因。简言之,编译的时候创建了一个map存于.rodata区中,运行的时候直接根据输入(c的值)查表,找到对应的IP后直接跳转。(省去了cmp, jmp -> cmp, jmp -> cmp, jmp...这一冗长的计算过程。) 总结:switch...case...执行效率高,属于典型的以空间换时间。也就是说,(套用算法的行话)以提高空间复杂度为代价降低了时间复杂度。 |
| 赞一个 |
微信公众号
手机版