80x86汇编与C语言-2

官方信息技术老师·10 页·深入(追求细节与边界)·0 次浏览·3 天前
x86汇编调用约定栈帧分析反汇编

80x86汇编与C语言-2

看清每行 C 代码背后的寄存器、栈帧与调用约定

按 空格/→ 演示下一步

1 / 10 页

全部页面点击任意一页,跳回舞台从这页播放

x86汇编调用约定栈帧分析反汇编

80x86汇编与C语言-2

看清每行 C 代码背后的寄存器、栈帧与调用约定

1第 1 页 · 80x86汇编与C语言-2

条件码

前面讲过算术指令会改写通用寄存器——其实CPU还多做一步:把这次运算的关键属性(结果是否为0、是否进位、是否溢出)编码成一组1-bit标志,塞进FLAGS寄存器的特定位。这就是条件码。

本质
FLAGS寄存器中6个1-bit标志位,硬件自动维护
六大标志
CF进位/ZF零/SF符号/OF溢出/PF奇偶/AF辅助进位
更新机制
ALU指令执行后硬件隐式写入,CMP/TEST不写结果只改标志
消费指令
Jcc条件跳转、SETcc写0/1、CMOVcc条件传送三类
C的底层映射
if/while/for皆翻译为cmp/test + Jcc,编译器据此分支
快递签收单对应 →条件码

验货后自动打勾/打叉,收件人只读不改——ALU隐式写,Jcc只读消费

OF=CinMSBCoutMSBOF = C_{in}^{MSB} \oplus C_{out}^{MSB}
2第 2 页 · 条件码

比较指令与测试指令

上一页讲了条件码是CPU的'小本本',但谁来填这本子?直接用SUB、AND会真的改数据——我们不想要这个副作用。CMP和TEST就是'只算不算、只测不存'的专用指令。

CMP: 只算不算
执行 dest - src,但不写回 dest,只把结果特征写入标志位
TEST: 只测不存
执行 dest AND src,不写回 dest,只把按位与的特征写入标志位
CMP 的对偶语义
无符号看 CF 判大小,有符号看 SF 与 OF 共同判大小;同一减法两种解读
TEST 的位检测
TEST eax, eax 检零(ZF=1 即全零);TEST eax, mask 检特定位
后必跟 Jcc
CMP/TEST 本身不跳转,必须配合条件跳转 Jcc 才形成 if/while/for
天平两端放物体看指针偏哪边对应 →CMP/TEST 只设标志位

指针告诉你大小关系,但两个数本身没被移动——纯'窥探',无副作用

CMP a,bab (flags only) TEST a,ba&b (flags only)\text{CMP}\ a,b \Rightarrow a-b\ \text{(flags only)} \quad\ \text{TEST}\ a,b \Rightarrow a \& b\ \text{(flags only)}
3第 3 页 · 比较指令与测试指令

读取条件码指令-1

CMP 和 TEST 帮你把比较结果写进了标志位,但标志位本身是隐式的,C 语言没法直接拿。这页要解决的就是:怎么把「条件成立与否」取出来,变成程序能用的 0/1。

只读不改
SETcc 只读 EFLAGS,不修改任何标志位——CMP/TEST 留下的状态原样保留
单字节目的地
目标只能是 8 位寄存器或内存字节,写入 0 或 1(假/真)
后缀即条件
SET 后字母即条件:E/NE、S/NS、O/NO 看单标志;G/GE/L/LE 带符号;A/AE/B/BE 无符号
典型场景
把 if 条件赋值转成 SETcc + 算术运算,避免分支预测失败带来的流水线冲刷
体检与体检报告对应 →CMP 与 SETcc

CMP 是体检,把指标写进报告(标志位);SETcc 是看报告,只填结论(0/1),不重做体检

dest[cond]{0,1}\text{dest} \leftarrow [\text{cond}] \in \{0, 1\}
4第 4 页 · 读取条件码指令-1

读取条件码指令-2

上一页讲的 SETcc、Jcc 都靠分支跳转,但现代 CPU 流水线最怕分支猜错。还有第三种读条件码的指令 CMOVcc——它不跳转,把两条路的结果都算好,按条件挑一个留下。

CMOVcc
条件为真时把源操作数复制到目的寄存器,为假时目的寄存器保持不变。
指令格式
CMOV<条件后缀> 源, 目的,如 CMOVE、CMOVL、CMOVA 等共十几种变体。
无分支 min/max
同时算两种候选值,用 CMOV 按条件挑选,避免 if-else 跳来跳去。
流水线友好
程序顺序不变,硬件不用猜分支,避免分支预测失败带来的清空代价。
使用代价
两条路径都白算,临时寄存器压力大;当分支极易预测时反而更慢。
岔路口对应 →CMOVcc 无分支选择

两条路都先铺好,走到路口按条件牌选一条;Jcc 是先猜一条走,猜错要回头

5第 5 页 · 读取条件码指令-2

x86-64下的读取条件码指令

上两页讲了 SET 写字节、JMP 改流程,都是'有条件地做某事'。还有第三条路——'条件决定选哪个值',这就是 CMOV 条件传送指令。

CMOVcc 指令族
CMOV 后接条件后缀:CMOVE/CMOVNE/CMOVL/CMOVG 等,与 SETcc/Jcc 共用同一套条件码家族
操作数约束
目标必须寄存器,源可寄存器或内存;操作数 16/32/64 位,不能像 SET 写 8 位
无分支优势
条件满足才传送,否则保持原值;避免 if 分支预测失败导致流水线清空,损失十几个周期
典型应用
编译器在 max/min、abs(x) 等两侧代价对称场景自动生成 CMOV;代价悬殊时仍退化为分支
铁路扳道工对应 →CMOV 条件传送

扳道工看信号旗决定火车走哪条轨道;CMOV 看条件码决定把哪个值送到目标寄存器

if cc then dstsrc\text{if } cc \text{ then } \text{dst} \leftarrow \text{src}
6第 6 页 · x86-64下的读取条件码指令

跳转指令

前面用 set 指令把条件码「读」出来,但光读不行动没用。跳转指令才是把决策落到执行顺序上的工具:该不该换行、去哪一段,全看条件码的脸色。

无条件跳转 jmp
无视条件码,直接改 PC;支持直接跳标号,也支持间接跳寄存器/内存
条件跳转 j*
14 种变体,按 CF/ZF/SF/OF 不同组合决定跳不跳
后缀编码条件
e/ne 看 ZF;g/l/ge/le 走有符号;a/b/ae/be 走无符号
典型应用模式
cmp+条件跳转实现 if-else;条件跳转+jmp 回跳实现循环;switch 走跳转表
x86-64 目标编码
跳转目标用相对 PC 偏移量;条件跳转限 8/32 位,间接 jmp 可用 64 位寄存器
游戏中遇到分叉路对应 →跳转指令

角色状态=条件码;岔路=跳转目标;j* 按状态选路,jmp 不管状态直接跳

7第 7 页 · 跳转指令

条件移动指令

跳转指令一旦预测失败,流水线要清空重来——代价惨烈。条件移动干脆不预测:先两边都算完,按条件码挑结果,流水线一根道走到底。

条件移动
满足条件码才把源操作数搬进目的寄存器,否则保留原值
无分支执行
不改变指令指针,流水线无需预测,CPI接近1
cmov族
cmove/cmovne/cmovg/cmovl/cmovs/cmovns等,对应各类条件码
?:三元式
把C语言 a = cond ? x : y 编译成无分支代码,避开分支预测
依赖关系
两条路径须都已算出,寄存器压力大;条件几乎不变时跳转更优
安检闸机对应 →条件移动

符合条件才放行(搬入),否则就地留下(不搬);队伍没换队形——控制流没分叉

8第 8 页 · 条件移动指令

本节要点

  • 比较与测试只更新条件码,不改源操作数
  • 跳转依据条件码选择路径,适合稀疏条件
  • 条件传送无分支,但受值可用性约束
  • x86-64下32位写条件码指令仅设置低8位
  • 同一逻辑条件可由多条互补条件码指令实现
延伸主题:条件码与编译器优化循环与分支的综合更多寻址与栈指令
9第 9 页 · 本节要点

课后思考

先自己琢磨,再对照参考答案。三题分别对应回顾、应用与迁移。

1为什么 `cmp` 与 `sub` 功能相近却要单设一条指令?

参考答案`cmp` 不写回结果,只置条件码,避免污染目的寄存器;语义清晰,也利于编译器优化。

2条件跳转和条件传送哪个更快?何时应优先选条件传送?

参考答案看场景:分支预测难时条件传送更快;适合简单赋值、计算量小、结果需多处使用的情形。

3若把条件码机制移植到 RISC-V 这类无标志位架构,思路该如何调整?

参考答案RISC-V没有标志位,比较结果本身就是值;用`slt`等指令产生0/1,再用分支或掩码合并结果。

10第 10 页 · 课后思考
80x86汇编与C语言-2 · 知识图解