如果条件较少的话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
;大于8时直接跳到default
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的地址