有小间隔值的switch-case语句,编译器使用了两级跳转表(two-level jump table)优化。
文章目录
- 完整代码
- 汇编代码
- 汇编特征分析
- 1. 范围检查和索引计算
- 2. 两级跳转表查找
- 3. 第一级表(索引表)分析
- 4. 第二级表(跳转表)分析
- 5. 各case块执行
- 关键特征总结
- 对应的C++代码推测
- 优化策略分析
完整代码
void switch_small_gaps(int value) {switch (value) {case 1: output(10); break;case 4: output(30); break;case 7: output(50); break;case 10: output(70); break;case 13: output(90); break;default: output(0); break;}
}
汇编代码
37: // 3. 有小间隔的值 - 可能使用小表优化38: void switch_small_gaps(int value) {
008F1FC0 push ebp
008F1FC1 mov ebp,esp
008F1FC3 sub esp,0C4h
008F1FC9 push ebx
008F1FCA push esi
008F1FCB push edi
008F1FCC lea edi,[ebp-4]
008F1FCF mov ecx,1
008F1FD4 mov eax,0CCCCCCCCh
008F1FD9 rep stos dword ptr es:[edi]
008F1FDB mov ecx,offset _FA199733_if语句@cpp (08FD058h)
008F1FE0 call @__CheckForDebuggerJustMyCode@4 (08F1348h)
008F1FE5 nop 39: switch (value) {
008F1FE6 mov eax,dword ptr [value]
008F1FE9 mov dword ptr [ebp-0C4h],eax
008F1FEF mov ecx,dword ptr [ebp-0C4h]
008F1FF5 sub ecx,1
008F1FF8 mov dword ptr [ebp-0C4h],ecx
008F1FFE cmp dword ptr [ebp-0C4h],0Ch
008F2005 ja $LN9 (08F2057h)
008F2007 mov edx,dword ptr [ebp-0C4h]
008F200D movzx eax,byte ptr [edx+8F2090h]
008F2014 jmp dword ptr [eax*4+8F2078h] 40: case 1: output(10); break;
008F201B push 0Ah
008F201D call output (08F105Ah)
008F2022 add esp,4
008F2025 jmp $LN9+0Ah (08F2061h) 41: case 4: output(30); break;
008F2027 push 1Eh
008F2029 call output (08F105Ah)
008F202E add esp,4
008F2031 jmp $LN9+0Ah (08F2061h) 42: case 7: output(50); break;
008F2033 push 32h
008F2035 call output (08F105Ah)
008F203A add esp,4
008F203D jmp $LN9+0Ah (08F2061h) 43: case 10: output(70); break;
008F203F push 46h
008F2041 call output (08F105Ah)
008F2046 add esp,4
008F2049 jmp $LN9+0Ah (08F2061h) 44: case 13: output(90); break;
008F204B push 5Ah
008F204D call output (08F105Ah)
008F2052 add esp,4
008F2055 jmp $LN9+0Ah (08F2061h) 45: default: output(0); break;
008F2057 push 0
008F2059 call output (08F105Ah)
008F205E add esp,4 46: }47: }
008F2061 pop edi
008F2062 pop esi
008F2063 pop ebx
008F2064 add esp,0C4h
008F206A cmp ebp,esp
008F206C call __RTC_CheckEsp (08F1258h)
008F2071 mov esp,ebp
008F2073 pop ebp
008F2074 ret
008F2075 nop dword ptr [eax]
008F2078 sbb esp,dword ptr [eax]
008F207A pop dword ptr [eax]
008F207C daa
008F207D and byte ptr [edi-70DFCD00h],cl
008F2083 add byte ptr [edi],bh
008F2085 and byte ptr [edi-70DFB500h],cl
008F208B add byte ptr [edi+20h],dl
008F208E pop dword ptr [eax]
008F2090 add byte ptr ds:[5050105h],al
008F2096 add al,byte ptr ds:[5050305h]
008F209C add al,0CCh
根据提供的汇编代码,这是一个有小间隔值的switch-case语句,编译器使用了**两级跳转表(two-level jump table)**优化。
汇编特征分析
1. 范围检查和索引计算
008F1FE6 mov eax,dword ptr [value]
008F1FE9 mov dword ptr [ebp-0C4h],eax
008F1FEF mov ecx,dword ptr [ebp-0C4h]
008F1FF5 sub ecx,1 ; value - 1 (索引调整)
008F1FF8 mov dword ptr [ebp-0C4h],ecx
008F1FFE cmp dword ptr [ebp-0C4h],0Ch ; 比较是否大于12
008F2005 ja 008F2057h ; 大于12跳转到default
2. 两级跳转表查找
008F2007 mov edx,dword ptr [ebp-0C4h] ; 加载索引
008F200D movzx eax,byte ptr [edx+8F2090h] ; 从第一级表加载字节
008F2014 jmp dword ptr [eax*4+8F2078h] ; 第二级跳转表查找
3. 第一级表(索引表)分析
地址8F2090h
开始的索引表包含13个字节(0-12):
8F2090h: 00 00 00 00 01 00 00 02 00 00 03 00 00 04
对应case值映射:
- 索引0-12对应value值1-13
- 有效case:索引0(1), 3(4), 6(7), 9(10), 12(13)
- 索引值:0→case1, 1→case4, 2→case7, 3→case10, 4→case13
4. 第二级表(跳转表)分析
地址8F2078h
开始的跳转表包含5个条目:
8F2078h: 1B 20 8F 00 ; 指向 case 1 (8F201Bh)
8F207Ch: 27 20 8F 00 ; 指向 case 4 (8F2027h)
8F2080h: 33 20 8F 00 ; 指向 case 7 (8F2033h)
8F2084h: 3F 20 8F 00 ; 指向 case 10 (8F203Fh)
8F2088h: 4B 20 8F 00 ; 指向 case 13 (8F204Bh)
5. 各case块执行
; case 1
008F201B push 0Ah ; 10
008F201D call output
008F2025 jmp 008F2061h; case 4
008F2027 push 1Eh ; 30
008F2029 call output
008F2031 jmp 008F2061h; case 7
008F2033 push 32h ; 50
008F2035 call output
008F203D jmp 008F2061h; case 10
008F203F push 46h ; 70
008F2041 call output
008F2049 jmp 008F2061h; case 13
008F204B push 5Ah ; 90
008F204D call output
008F2055 jmp 008F2061h; default
008F2057 push 0
008F2059 call output
关键特征总结
-
两级跳转表:
- 第一级:字节索引表,将输入值映射到跳转表索引
- 第二级:地址跳转表,执行实际跳转
-
内存优化: 使用字节数组作为索引表,比直接使用地址表更节省内存
-
稀疏值处理: 虽然case值有固定间隔(3),但编译器仍然使用两级表而不是简单计算
-
O(1)时间复杂度: 通过两级查表实现常数时间跳转
-
紧凑的范围检查: 检查value是否在1-13范围内
对应的C++代码推测
void switch_small_gaps(int value) {switch (value) {case 1: output(10); break;case 4: output(30); break;case 7: output(50); break; case 10: output(70); break;case 13: output(90); break;default: output(0); break;}
}
优化策略分析
编译器选择两级跳转表优化是因为:
- case值稀疏但范围紧凑:值分布在1-13范围内
- 内存效率:字节索引表(13字节) + 地址跳转表(20字节) = 33字节
- 相比单级跳转表:直接使用地址表需要13×4=52字节,节省了19字节
- 相比条件链:提供O(1)性能而非O(n)
这种优化在case值相对稀疏但分布在一定连续范围内时特别有效,在性能和内存使用之间取得了良好平衡。