004、观察各种表达式的求值过程
从数据流恢复表达式
机器代码不会保留括号和源码排版。恢复表达式时要跟踪每个值的来源、运算和写回目标。
1 | mov eax, [a] |
候选语义是 result = a * b + c,但还需结合宽度、符号和调用上下文确认。
算术与赋值
| 源码语义 | 常见指令 |
|---|---|
| 加减 | ADD、SUB、LEA |
| 乘法 | IMUL、MUL、移位与加法组合 |
| 除法 | IDIV、DIV、乘倒数优化 |
| 取负 | NEG |
| 自增自减 | INC/DEC 或 ADD/SUB 1 |
复合赋值通常直接对原位置读改写:
1 | add dword ptr [value], 5 |
LEA 不访问其计算出的目标内存,常被编译器用于整数运算:
1 | lea eax, [ecx+ecx*4] ; eax = ecx * 5 |
溢出与截断
不同宽度的临时值会发生截断或扩展。分析时记录:
- 运算发生在哪个宽度。
- 写回时保留多少位。
- 后续使用有符号还是无符号语义。
关系运算
CMP a, b 近似执行 a - b,只更新标志位,不保存结果。
| 语义 | 有符号跳转 | 无符号跳转 |
|---|---|---|
| 相等 | JE |
JE |
| 不等 | JNE |
JNE |
| 小于 | JL |
JB |
| 大于 | JG |
JA |
| 小于等于 | JLE |
JBE |
| 大于等于 | JGE |
JAE |
逻辑短路
1 | if (a != 0 && b / a > 2) { ... } |
编译器会先检查 a,条件失败便跳过第二个表达式。这不仅提高效率,也避免除零。
1 | 判断 a ─失败→ 结束 |
条件表达式
三目运算可能编译成分支,也可能使用 CMOVcc 或位运算实现无分支选择。不能只凭是否出现跳转判断源码是否使用三目运算。
位运算
典型语义:
AND:清位、掩码、对齐。OR:置位、组合标志。XOR:翻转、比较、清零寄存器。TEST:按位与后只更新标志位。SHL/SHR/SAR:乘除 2 的幂、字段提取。
1 | and eax, 0FFFFFFF0h ; 向下按 16 字节对齐 |
优化如何改变表达式
常见优化包括:
- 常量折叠与常量传播。
- 公共子表达式消除。
- 强度削弱,例如乘法改为移位或 LEA。
- 无用代码删除。
- 条件移动与分支消除。
- 循环不变量外提。
优化后,机器代码更接近“等价语义”,不一定保留源码逐项求值顺序。
恢复表达式的步骤
- 给寄存器标注当前语义,不沿用硬件名称思考。
- 找出每次内存读取和最终写回。
- 按依赖关系组成表达式树。
- 判断符号、宽度和溢出语义。
- 在调用点或测试输入中验证。
学习检查清单
| 检查项 | 状态 |
|---|---|
| 能从数据依赖恢复简单算术表达式 | 待复盘 |
| 能区分有符号和无符号条件跳转 | 待复盘 |
| 能识别逻辑短路形成的提前跳转 | 待复盘 |
| 能解释 LEA 用于整数运算的情况 | 待复盘 |
| 能列举至少四种编译优化 | 待复盘 |
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Ruiqy~!





















