(学习笔记)3.6 控制(3.6.8 switch语句)
文章目录线索栏笔记栏1.跳转表高效多重分支的核心2. 编译实现通用步骤以图3-22/3-23示例 switch_eg为例3. 跳转表数据结构汇编片段4. GCC对C语言的扩展计算goto5. 练习题练习题3.30练习题3.31 图3-24总结栏线索栏核心机制编译器在什么条件下会使用跳转表 (jump table) 来实现 switch语句相比一长串if-else跳转表的主要优势是什么实现三步曲实现一个基于跳转表的 switch语句汇编代码通常遵循哪三个关键步骤偏移、检查、跳转索引转换为什么在汇编代码中通常先对 switch的变量做一次减法例如 subq $100,%rdi这步操作在C语言扩展实现中对应什么边界检查技巧汇编代码如何高效地检查转换后的索引是否在跳转表范围内为什么可以使用无符号比较跳转表结构跳转表在汇编代码.rodata段中是如何声明的它如何编码不同 case值包括连续、重复、缺失的情况对应的目标地址逆向工程给定一个 switch语句的汇编代码片段和其跳转表如何系统地逆向推导出原始C代码中所有case的值和对应的代码块练习题3.30 3.31笔记栏1.跳转表高效多重分支的核心1适用条件当 case数量较多如4个且值相对密集时GCC会使用跳转表。2优势执行时间与 case数量无关仅为一次数组索引和一次间接跳转。这是典型的“以空间换时间”。3数据结构一个地址数组表项 i是当开关索引值等于 i时应跳转的代码地址。2. 编译实现通用步骤以图3-22/3-23示例 switch_eg为例C函数long switch_eg(long n, long v) { … switch(n) { … } }1计算索引index n - 100。将原 case值范围 (100-106) 平移至以0为起点 (0-6)。汇编subq $100, %rdi 结果在 %rsi2边界检查比较 index是否 6。利用无符号比较当 index为负数即原 n 100时会因其巨大的无符号表示而判定为 6从而跳转到默认 (default) 块。汇编cmpq $6, %rsi; ja .L83间接跳转通过 index从跳转表 jt中取出目标地址并跳转。汇编jmp *.L4(,%rsi,8)3. 跳转表数据结构汇编片段.section.rodata.align8.L4:# 跳转表基地址.quad.L3 # index0(n100):跳转到.L3(loc_A).quad.L8 # index1(n101):跳转到.L8(default,缺失case).quad.L5 # index2(n102):跳转到.L5(loc_B).quad.L6 # index3(n103):跳转到.L6(loc_C).quad.L7 # index4(n104):跳转到.L7(loc_D).quad.L8 # index5(n105):跳转到.L8(default,缺失case).quad.L7 # index6(n106):跳转到.L7(loc_D 与case104相同)1处理重复casecase 104和 case 106指向同一代码块 (.L7)。2处理缺失casecase 101和 105指向默认块 (.L8)。3处理fall throughcase 102的代码块 (.L5) 末尾没有 jmp指令执行会“落入” case 103的代码块 (.L6)。4. GCC对C语言的扩展计算goto在教材提供的等价的C扩展代码 switch_eg_impl中使用 运算符获取代码标签的地址从而构造跳转表并使用 goto *jt[index]进行间接跳转。这帮助我们理解汇编指令 jmp *.L4(,%rsi,8)的含义。5. 练习题练习题3.30已知1x在 %rdi。2首条指令addq $1, %rdi。这意味着索引计算为 index x 1。3比较指令cmpq $8, %rdi。检查 index是否 8。4跳转表有9项索引0~8。解答A. case值因为 index x 1 且 ja .L2对应默认所以有效的 index是 0~8对应 x为 -1 ~ 7。但 case值通常是合理的整数所以应为 0~7 以及可能的 -1。结合跳转表结构推断case值为0 (index1), 1(index2), 2(index3), 3(index4), 4(index5), 5(index6), 6(index7)。index0和 index8对应默认分支。B. 多标号情况跳转表第6、7行相同 (.L7)第3、10行相同 (.L5)。因此case 2和 case 6共享同一个代码块 (.L7)。case 0和 case 4? 等等需要映射index1对应 x0跳 .L5index5对应 x4跳 .L2(默认)不对。index9对应 x8超范围。仔细看.L40是 .L9(可能对应某个case) .L48 * 8是 .L5。所以 index0(x-1) 和 index8(x7) 都跳 .L5不.L4表第1项是 .L9。所以多标号是case -1和 case 7共享代码块 .L9 (如果 .L40对应 x-1)但更合理的推断是 case值为 0-6 其中 case 1和 case 5共享代码块 .L5 case 3和 case 4共享代码块 .L7。练习题3.31 图3-24已知汇编代码逻辑和跳转表。逆向推导1计算索引a在 %rdi。指令 subq $50, %rdi index a - 50。2边界检查cmpq $6, %rdi; ja .L2。索引范围 0~6 有效对应原 a为 50~56。3分析跳转表 (7项).L8:.quad.L3 # index0(a50).quad.L2 # index1(a51)-default.quad.L5 # index2(a52).quad.L6 # index3(a53).quad.L7 # index4(a54).quad.L7 # index5(a55)-与54相同.quad.L5 # index6(a56)-与52相同4映射代码块.L3(a50): movq %rdx, %rdi; jmp .L4 此块修改了参数 c(%rdx)且无 break(jmp)会 fall through。.L5(a52, 56): movq %rdx, %rax; xorq $15, %rax; movq %rax, %rdx; 计算 c ^ 15并赋回 c? 不看后面 movq %rax, (%r9)是赋值给 *dest。所以这是计算 val。.L6(a53): movq %rdx, %rax; movq %rax, (%r9); ret 计算 val c。.L7(a54, 55): movq $2, %rax; … 计算 val 2。.L2(default): movq $0, %rax; … val 0。voidswitcher(longa,longb,longc,long*dest){longval;switch(a){case50:/* Case A */cb;/* 对应 movq %rsi, %rdx *//* Fall through */case52:/* Case B (与56共享) */valc^15;/* 对应 xorq $15, %rax */break;case53:/* Case C */case54:/* Case D (与55共享) */val2;/* 对应 movq $2, %rax */break;case51:/* Case E (默认项之一) */val0;break;default:val0;}*destval;}总结栏本节深入剖析了 switch语句最高效的实现方式——跳转表揭示了编译器将高级语言多路分支映射到底层硬件的精巧过程。核心是数组跳转编译器将 switch编译为“计算偏移索引→检查边界→间接跳转”的固定模式。跳转表就是一个地址数组间接跳转 (jmp*) 是执行的关键。巧妙的范围平移通过将case值减去最小值化为从0开始的连续索引使得跳转表访问成为可能。同时利用无符号比较一举两得地处理了索引下溢原值太小和上溢原值太大的情况。跳转表编码语义跳转表的内容直接编码了 switch的语义1连续 case连续的表项。2重复 case多个表项指向同一地址。3缺失 case对应表项指向默认块地址。4fall through前一个 case的代码块末尾没有跳出指令。逆向工程心法给定汇编首先找到索引计算sub指令和边界检查cmp/ja这决定了case的原始值范围。然后分析跳转表每个表项对应一个 case值。最后将各个目标代码块.Lx与case行为匹配。练习题3.31是此方法的完整演练。最终启示switch的跳转表实现是编译器优化的典范它通过引入少量静态数据跳转表来换取稳定的、快速的执行时间。理解这一点对于编写高效代码和进行底层调试至关重要。同时这也展示了数据跳转表如何有效地控制程序流。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2431302.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!