如果条件较少的话switch和if都是通过逐项比较跳转实现的,条件较多时(具体取决于编译器)会生成一张地址表用于快速跳转
测试代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| #include<iostream>
void test(int x) {
using namespace std;
switch (x) { case 1: cout << "A\n"; break; case 2: cout << "B\n"; break; case 4: cout << "C\n"; break; case 5: cout << "D\n"; break; case 8: cout << "E\n"; break; default: cout << "default\n"; break; } return; }
void main() {
__asm { mov eax, eax mov eax, eax mov eax, eax }
test(3); return; }
|
反汇编
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
| 3: void test(int x) { 00172470 push ebp 00172471 mov ebp,esp 00172473 sub esp,0C4h 00172479 push ebx 0017247A push esi 0017247B push edi 0017247C lea edi,[ebp-4] 0017247F mov ecx,1 00172484 mov eax,0CCCCCCCCh 00172489 rep stos dword ptr es:[edi] 0017248B mov ecx,17E029h 00172490 call 00171375 4: 5: using namespace std 6: 7: switch (x) { 00172495 mov eax,dword ptr [ebp+8] 00172498 mov dword ptr [ebp+FFFFFF3Ch],eax 0017249E mov ecx,dword ptr [ebp+FFFFFF3Ch] 001724A4 sub ecx,1 001724A7 mov dword ptr [ebp+FFFFFF3Ch],ecx 001724AD cmp dword ptr [ebp+FFFFFF3Ch],7 001724B4 ja 0017252C 001724B6 mov edx,dword ptr [ebp+FFFFFF3Ch] 001724BC jmp dword ptr [edx*4+00172554h] 8: case 1: 9: cout << "A\n" 001724C3 push 179B30h 001724C8 mov eax,dword ptr ds:[0017D0C8h] 001724CD push eax 001724CE call 001711A4 001724D3 add esp,8 10: break 001724D6 jmp 0017253F 11: case 2: 12: cout << "B\n" 001724D8 push 179B34h 001724DD mov eax,dword ptr ds:[0017D0C8h] 001724E2 push eax 001724E3 call 001711A4 001724E8 add esp,8 13: break 001724EB jmp 0017253F 14: case 4: 15: cout << "C\n" 001724ED push 179B38h 001724F2 mov eax,dword ptr ds:[0017D0C8h] 001724F7 push eax 001724F8 call 001711A4 001724FD add esp,8 16: break 00172500 jmp 0017253F 17: case 5: 18: cout << "D\n" 00172502 push 179B3Ch 00172507 mov eax,dword ptr ds:[0017D0C8h] 0017250C push eax 0017250D call 001711A4 00172512 add esp,8 19: break 00172515 jmp 0017253F 20: case 8: 21: cout << "E\n" 00172517 push 179B40h 0017251C mov eax,dword ptr ds:[0017D0C8h] 00172521 push eax 00172522 call 001711A4 00172527 add esp,8 22: break 0017252A jmp 0017253F 23: default: 24: cout << "default\n" 0017252C push 179B44h 00172531 mov eax,dword ptr ds:[0017D0C8h] 00172536 push eax 00172537 call 001711A4 0017253C add esp,8 25: break 26: } 27: return 28: } 0017253F pop edi 00172540 pop esi 00172541 pop ebx 00172542 add esp,0C4h 00172548 cmp ebp,esp 0017254A call 0017128A 0017254F mov esp,ebp 00172551 pop ebp 00172552 ret 00172553 nop 00172554 ret 00172555 and al,17h 00172557 add al,bl 00172559 and al,17h 0017255B add byte ptr ds:[24ED0017h],ch 00172562 pop ss 00172563 add byte ptr [edx],al 00172565 and eax,252C0017h 0017256A pop ss 0017256B add byte ptr ds:[25170017h],ch 00172572 pop ss 00172573 add ah,cl
|
分析关键处
1 2 3 4 5 6 7 8 9 10 11
| mov eax,dword ptr [ebp+8] mov dword ptr [ebp+FFFFFF3Ch],eax mov ecx,dword ptr [ebp+FFFFFF3Ch] sub ecx,1
mov dword ptr [ebp+FFFFFF3Ch],ecx cmp dword ptr [ebp+FFFFFF3Ch],7 ja 0017252C
mov edx,dword ptr [ebp+FFFFFF3Ch] jmp dword ptr [edx*4+00172554h]
|
ECX-1并不是固定的,取决于条件表达式中最小值的常量,然后和7比较(也就是大于8时直接就跳到default)
编译器在编译时就已经预先填充了一个地址表(称之为大表)用以索引跳转地址(以4字节为单位),将参数减去最小常量值是为了方便索引
00172554h处内存如下:

从上面的反汇编可以得知0017252C就是default的地址,如果条件常亮不是连续的,编译器会在大表中间的空隙中填充default的地址,比如说上面的测试代码swtich一共5个条件常量,但1-8之间有8个选择区间(swtich表达式的结果不能为浮点数),所以编译器生成的大表一共有32字节/4 = 8个地址
大表中储存的地址顺序和源码中case从小到大排列一样,如果swtich的顺序不是按从小到大的,编译器生成的大表中也是按从小到大储存的地址,比如说上面的代码,case 1的地址永远在最下面
如果传个1进去,1-1=0,0*4+00172554h=00172554h, 00172554h里面储存的就是case 1的地址