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>3 M) i; ~$ y5 G/ o, ?+ k# d0 A; c$ z9 ]1 w1 G' O6 z' y static int foo_ifelse(char c)" {- g- |# d1 c$ q { if (c == '0' || c == '1') { c += 1; } else if (c == 'a' || c == 'b') {6 y9 E2 k" j* [& n( Q c += 2; } else if (c == 'A' || c == 'B') { c += 3; } else {0 K. f$ J% d7 L% x" {+ P$ \3 O) l c += 4;+ I" S, K% J: }; ?3 G } , m3 g- I4 |* [9 O! `' b return (c); } static int* Q. A$ h4 V/ m$ b5 ^7 ~, } foo_switch(char c)+ A* m- b; ]! l { switch (c) { case '1': case '0': c += 1; break; case 'b':: n V- u3 k# ^ case 'a': c += 2; break; case 'B':2 y* a2 ?& } m5 R) @ case 'A': c += 3; break; default: c += 4; break; }9 F& g9 O9 Y8 d) L# R- q+ W 9 l# s5 ~' O! g0 m1 i6 \# C return (c); }& K5 j; @! N7 V . [# @ E/ j: u int4 {/ x5 Q3 P* V8 S, |+ R: v& O main(int argc, char **argv)- Q0 n# w. D$ i H- x" w {" d: C8 X& y& E% O9 c, A/ A, I int m1 = foo_ifelse('0');2 ~7 e% @; D2 h) E% \ int m2 = foo_ifelse('1'); int n1 = foo_switch('a'); int n2 = foo_switch('b'); ~3 `& Z7 z3 i" e4 O; O, Y/ u8 w (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()# b9 b8 Y) g' L( I(gdb) set disassembly-flavor intel" R' t$ O& t2 i. n& b2 b (gdb) disas /m foo_ifelse Dump of assembler code for function foo_ifelse:1 Z4 T6 h9 q f! V* R* R! y 4 {% H0 |: V8 }1 Z1 H% F8 Z/ F 0x0804841d <+0>: push ebp 0x0804841e <+1>: mov ebp,esp 0x08048420 <+3>: sub esp,0x47 S i8 p( i/ z8 ?/ G- G! h 0x08048423 <+6>: mov eax,DWORD PTR [ebp+0x8]4 M! h! R7 J) p: Q5 k; ~ 0x08048426 <+9>: mov BYTE PTR [ebp-0x4],al4 _* q% w# Y4 d f0 g+ b* M7 s7 E. L 5 if (c == '0' || c == '1') {8 f2 S3 P- B. }/ Y4 U 0x08048429 <+12>: cmp BYTE PTR [ebp-0x4],0x30 0x0804842d <+16>: je 0x8048435 <foo_ifelse+24> 0x0804842f <+18>: cmp BYTE PTR [ebp-0x4],0x319 n% t* M, A; ?: q, ~( s 0x08048433 <+22>: jne 0x8048441 <foo_ifelse+36> 9 D5 y2 R; \( S 6 c += 1; 0x08048435 <+24>: movzx eax,BYTE PTR [ebp-0x4]9 w }; Y7 V! ^$ T8 P/ w 0x08048439 <+28>: add eax,0x1 0x0804843c <+31>: mov BYTE PTR [ebp-0x4],al 0x0804843f <+34>: jmp 0x804847b <foo_ifelse+94>6 Z/ p1 c- q+ b 7 } else if (c == 'a' || c == 'b') {7 ^- S; O8 f' D/ ]8 r7 r 0x08048441 <+36>: cmp BYTE PTR [ebp-0x4],0x61 0x08048445 <+40>: je 0x804844d <foo_ifelse+48>5 h1 `% P; u$ q4 M 0x08048447 <+42>: cmp BYTE PTR [ebp-0x4],0x629 [( V# }. @& R* S) c! b 0x0804844b <+46>: jne 0x8048459 <foo_ifelse+60> 8 c += 2;# l% \; b8 T" s! g1 W4 e+ u k 0x0804844d <+48>: movzx eax,BYTE PTR [ebp-0x4] 0x08048451 <+52>: add eax,0x2 0x08048454 <+55>: mov BYTE PTR [ebp-0x4],al0 o1 M. f' E- ?5 o0 f9 x 0x08048457 <+58>: jmp 0x804847b <foo_ifelse+94>3 U' c+ b, g# ~5 L' z 7 `6 Z) A" ^% @( i% R# a( U0 h 9 } else if (c == 'A' || c == 'B') {1 {% Z% D L, R5 M 0x08048459 <+60>: cmp BYTE PTR [ebp-0x4],0x41+ x2 U! `, O; g. [1 i 0x0804845d <+64>: je 0x8048465 <foo_ifelse+72>3 f+ W8 N4 Z2 @# \ 0x0804845f <+66>: cmp BYTE PTR [ebp-0x4],0x42 0x08048463 <+70>: jne 0x8048471 <foo_ifelse+84> $ ~0 D* ~, B* l# L 10 c += 3; 0x08048465 <+72>: movzx eax,BYTE PTR [ebp-0x4] 0x08048469 <+76>: add eax,0x3 0x0804846c <+79>: mov BYTE PTR [ebp-0x4],al 0x0804846f <+82>: jmp 0x804847b <foo_ifelse+94> 11 } else { 12 c += 4;3 g a; z7 ^0 ?. N: n 0x08048471 <+84>: movzx eax,BYTE PTR [ebp-0x4]0 F* w3 H, Q5 F8 k/ a1 k+ K 0x08048475 <+88>: add eax,0x4 0x08048478 <+91>: mov BYTE PTR [ebp-0x4],al . g/ i: g5 e- u; }3 G& u0 E 13 }/ K. d6 h" ^: g: _" R1 P( a% F% q 14 15 return (c);3 |( {* P5 R' |3 j, |6 N 0x0804847b <+94>: movsx eax,BYTE PTR [ebp-0x4]3 V; Q* s+ ~6 m W 16 } 0x0804847f <+98>: leave 0x08048480 <+99>: ret/ V( [' K+ s1 T1 t, O2 s; W9 V ' ~; Q J: w+ m1 v" P: B, M End of assembler dump.3 W+ S; h) L2 |4 n/ ? (gdb)o 反汇编foo_ifelse() (gdb) set disassembly-flavor intel (gdb) disas /m foo_ifelse Dump of assembler code for function foo_ifelse:, ]# D3 H- u& a9 V 4 {+ R0 Q/ u* l) q* w 0x0804841d <+0>: push ebp$ y; o1 ?4 K; Z 0x0804841e <+1>: mov ebp,esp- R/ g8 V% Z& I5 f: ~; r# S 0x08048420 <+3>: sub esp,0x44 w: d# g. u/ ~/ J% ] 0x08048423 <+6>: mov eax,DWORD PTR [ebp+0x8]6 k, w5 x" I: r) N( H. p$ j 0x08048426 <+9>: mov BYTE PTR [ebp-0x4],al* {7 g: f+ l# d7 q" r0 d% U ) j$ s7 P6 S; n0 ^, S9 G5 Y, L2 L8 { 5 if (c == '0' || c == '1') { 0x08048429 <+12>: cmp BYTE PTR [ebp-0x4],0x307 a6 @+ Y% k; c. g& ? 0x0804842d <+16>: je 0x8048435 <foo_ifelse+24>+ i# H G9 l5 |6 S/ E/ _: q 0x0804842f <+18>: cmp BYTE PTR [ebp-0x4],0x31: [. {7 w( o$ d) x; f. b 0x08048433 <+22>: jne 0x8048441 <foo_ifelse+36>+ E$ z+ \* p8 L* g 6 c += 1;- [7 C2 y9 c3 c5 E! C1 C/ o7 S 0x08048435 <+24>: movzx eax,BYTE PTR [ebp-0x4] 0x08048439 <+28>: add eax,0x1 0x0804843c <+31>: mov BYTE PTR [ebp-0x4],al( y# s$ S1 a3 S3 x. B* ] 0x0804843f <+34>: jmp 0x804847b <foo_ifelse+94>6 H; c6 k9 C l8 b5 q! L- S' q 7 } else if (c == 'a' || c == 'b') {+ |* T2 }' j$ Z5 f U 0x08048441 <+36>: cmp BYTE PTR [ebp-0x4],0x61 0x08048445 <+40>: je 0x804844d <foo_ifelse+48> 0x08048447 <+42>: cmp BYTE PTR [ebp-0x4],0x62 0x0804844b <+46>: jne 0x8048459 <foo_ifelse+60> ( H' C& Q. s. F* x9 Y, W 8 c += 2;& y$ |$ |4 y& E3 M; z: Y: g9 B 0x0804844d <+48>: movzx eax,BYTE PTR [ebp-0x4] 0x08048451 <+52>: add eax,0x23 J8 o4 l4 Z3 n T# E. f; | 0x08048454 <+55>: mov BYTE PTR [ebp-0x4],al' Y* F9 ~$ P9 P+ \3 q' Y5 A% C& @ 0x08048457 <+58>: jmp 0x804847b <foo_ifelse+94> 9 } else if (c == 'A' || c == 'B') {- r4 V4 ~0 a4 u: x& y* ~4 |1 T 0x08048459 <+60>: cmp BYTE PTR [ebp-0x4],0x41' y4 v# H2 @# z" F- ~ 0x0804845d <+64>: je 0x8048465 <foo_ifelse+72> 0x0804845f <+66>: cmp BYTE PTR [ebp-0x4],0x42 0x08048463 <+70>: jne 0x8048471 <foo_ifelse+84>/ R w4 M9 e T5 ?+ D! L) |8 a 3 _! a/ j; \: d2 l7 T 10 c += 3;5 M; R6 h" M+ |3 M8 F5 F: l# F1 c 0x08048465 <+72>: movzx eax,BYTE PTR [ebp-0x4]* `3 r& j: B- e6 B# t% r1 R 0x08048469 <+76>: add eax,0x3: ^/ X! u) K' b1 f 0x0804846c <+79>: mov BYTE PTR [ebp-0x4],al; c7 ?5 `/ M4 r 0x0804846f <+82>: jmp 0x804847b <foo_ifelse+94>. M1 \5 s5 z- R7 z + L7 ?2 ~8 l, K: O9 b 11 } else {# k$ q: o! ]% l# p, A& S# x 12 c += 4; 0x08048471 <+84>: movzx eax,BYTE PTR [ebp-0x4] 0x08048475 <+88>: add eax,0x4 d6 d3 i4 s" U, r6 s. V 0x08048478 <+91>: mov BYTE PTR [ebp-0x4],al, w' `9 h3 l, F ( T' S4 U, n) n3 b& X0 M! A+ _ 13 } 14 15 return (c); 0x0804847b <+94>: movsx eax,BYTE PTR [ebp-0x4] K" o- e( i- ?9 u1 _% z$ W 16 } 0x0804847f <+98>: leave: ~6 g; ? {4 S2 a! r 0x08048480 <+99>: ret 0 z, e( l4 K: s- y9 D1 y End of assembler dump.- P& |* A7 d! ^+ Q2 a5 ~/ L (gdb)9 y! X" C; k' h o 反汇编foo_switch() (gdb) set disassembly-flavor intel% J t; w6 W: u6 W(gdb) disas /m foo_switch, i3 @3 _" J0 c4 X8 g$ ` Dump of assembler code for function foo_switch: u. ]2 X& @5 o8 w$ L. b 20 {4 V. ]8 C: M B: u 0x08048481 <+0>: push ebp+ N6 ~# x2 j9 I1 Q! q0 | 0x08048482 <+1>: mov ebp,esp 0x08048484 <+3>: sub esp,0x4 0x08048487 <+6>: mov eax,DWORD PTR [ebp+0x8] 0x0804848a <+9>: mov BYTE PTR [ebp-0x4],al : _' m" y9 V$ N# I% s) p 21 switch (c) {3 u( J3 A$ R! H# l 0x0804848d <+12>: movsx eax,BYTE PTR [ebp-0x4] 0x08048491 <+16>: sub eax,0x30 0x08048494 <+19>: cmp eax,0x32 0x08048497 <+22>: ja 0x80484c6 <foo_switch+69> 0x08048499 <+24>: mov eax,DWORD PTR [eax*4+0x80485f0] 0x080484a0 <+31>: jmp eax 22 case '1':7 q$ [9 O1 x* q: G' ^ 23 case '0': c += 1; break;6 v; `9 |9 |& B, l: ~% L* C" k8 t) ` 0x080484a2 <+33>: movzx eax,BYTE PTR [ebp-0x4] 0x080484a6 <+37>: add eax,0x15 V) ]$ Q1 h) n 0x080484a9 <+40>: mov BYTE PTR [ebp-0x4],al5 G: O- s7 Q2 v$ |/ F3 x" S3 k 0x080484ac <+43>: jmp 0x80484d1 <foo_switch+80>. E$ D5 u; l$ l 24 case 'b':+ E0 z! H0 V3 A1 N, J7 e) m 25 case 'a': c += 2; break;# U. r( z" j% w7 \ 0x080484ae <+45>: movzx eax,BYTE PTR [ebp-0x4]: t8 K! @2 f: N 0x080484b2 <+49>: add eax,0x2& S# t6 B( h% X! _1 J- \ 0x080484b5 <+52>: mov BYTE PTR [ebp-0x4],al$ u; p6 \6 K# i& M4 s. W% h: E/ R 0x080484b8 <+55>: jmp 0x80484d1 <foo_switch+80> 6 N6 m% M& @. M5 b7 s' `7 f 26 case 'B':9 g: Y+ f: t. {2 K$ U, ^# ? 27 case 'A': c += 3; break;8 k+ s. W! J* o) W [) S c! P 0x080484ba <+57>: movzx eax,BYTE PTR [ebp-0x4]# D1 K2 P3 s; y) O 0x080484be <+61>: add eax,0x3' _+ z: f1 o- x 0x080484c1 <+64>: mov BYTE PTR [ebp-0x4],al" R- L% N" T: I" J 0x080484c4 <+67>: jmp 0x80484d1 <foo_switch+80>2 G' }+ d( y! O 28 default: c += 4; break; 0x080484c6 <+69>: movzx eax,BYTE PTR [ebp-0x4] 0x080484ca <+73>: add eax,0x4; X: W" a2 D2 D5 k 0x080484cd <+76>: mov BYTE PTR [ebp-0x4],al/ R& v8 m" x* N7 C 0x080484d0 <+79>: nop9 \0 `" U2 c' O! Z* j1 y & V/ s! D0 M# E& A# F 29 } 30 31 return (c); 0x080484d1 <+80>: movsx eax,BYTE PTR [ebp-0x4] 32 }# Y) M0 ~( E9 H5 w8 u 0x080484d5 <+84>: leave9 s3 s& ~' F8 a) r 0x080484d6 <+85>: ret End of assembler dump.6 ~4 c. O7 B5 X( r (gdb)2 [/ S8 a( I/ q9 U' U 分析:
21 switch (c) { 0x0804848d <+12>: movsx eax,BYTE PTR [ebp-0x4] 0x08048491 <+16>: sub eax,0x30 0x08048494 <+19>: cmp eax,0x32; i- e N7 p. ?/ S 0x08048497 <+22>: ja 0x80484c6 <foo_switch+69>+ {8 q+ z0 y, h" B 0x08048499 <+24>: mov eax,DWORD PTR [eax*4+0x80485f0] 0x080484a0 <+31>: jmp eax ../ G% q7 R' [7 h& K a+ e6 d 注意: 第17行 jmp eax 也就是说,当c的取值不同,是什么机制保证第17行能跳转到正确的位置开始执行呢? 第16行: eax = [eax * 4 + 0x80485f0] 搞清楚了从地址0x80485f0开始,对应的内存里面的内容也就回答了刚才的问题。 执行完第16行后,
通过gdb查看对应的内存,确实如此! >>> ord('1') - 0x30>>> ord('0') - 0x309 d7 E6 e B- b. d) N# S- n (gdb) x /2wx 0*4+0x80485f0( r/ n4 ?- L2 j& e) y: u3 b/ B 0x80485f0: 0x080484a2 0x080484a2 9 o3 @8 y9 Q, M, c >>> ord('b') - 0x30 >>> ord('a') - 0x305 s7 R: c) A* g) W: O# c: I (gdb) x /2wx 49*4+0x80485f08 y# }' o. W* S9 O+ N) W& [7 O- J! h 0x80486b4: 0x080484ae 0x080484ae & X$ m5 ]0 \% C& h$ Y: ]7 e& U5 z' y >>> ord('B') - 0x30 >>> ord('A') - 0x30# d3 x: {( L, C8 r& `+ i/ ] (gdb) x /2wx 17*4+0x80485f0 0x8048634: 0x080484ba 0x080484ba 那么,我们可以大胆的猜测,虽然c的取值不同但是跳转的IP确实是精准无误的,一定是编译阶段就被设定好了,果真如此吗?接下来分析一下对应的二进制文件foo, 第四步,使用objdump查看foo,$ objdump -D foo > /tmp/x! J, r( K G4 a" m* H& S$ vim /tmp/x 509 Disassembly of section .rodata:/ Y" C0 F) z; G# k ... 518 80485f0: a2 84 04 08 a2 mov %al,0xa2080484 519 80485f5: 84 04 08 test %al,(%eax,%ecx,1) ...6 z* i2 G6 Q7 F4 f! A 534 8048630: c6 84 04 08 ba 84 04 movb $0x8,0x484ba08(%esp,%eax,1)" H7 V1 @) c: Q% [% [8 r/ y# q0 w 535 8048637: 081 }, `% Z4 M& g8 J 536 8048638: ba 84 04 08 c6 mov $0xc6080484,%edx* h: B& a. m( g4 } ...+ }8 g2 p1 r% k( h 566 80486b0: c6 84 04 08 ae 84 04 movb $0x8,0x484ae08(%esp,%eax,1) 567 80486b7: 08! k C" V6 {& T: {( U 568 80486b8: ae scas %es %edi),%al6 g; ^7 \9 m! F" [- |2 @. h/ j569 80486b9: 84 04 08 test %al,(%eax,%ecx,1)2 }; g% W6 T' ]! h9 [" A ... 在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]+ M h8 j* K# K. I( E: f- \17 0x080484a0 <+31>: jmp eax+ z$ \8 W( ^( }7 W 到此为止,我们已经搞清楚了为什么switch...case...语句相对于if...else if...else...来说执行效率要高的根本原因。简言之,编译的时候创建了一个map存于.rodata区中,运行的时候直接根据输入(c的值)查表,找到对应的IP后直接跳转。(省去了cmp, jmp -> cmp, jmp -> cmp, jmp...这一冗长的计算过程。) 总结:switch...case...执行效率高,属于典型的以空间换时间。也就是说,(套用算法的行话)以提高空间复杂度为代价降低了时间复杂度。 ! u+ [% R" Q/ V |
| 赞一个 |
微信公众号
手机版