尧图精选

8086/8088 FLAGS寄存器详解:9个标志位从背到用的底层调试指南

🕒 发布时间:2026/11/6 1:08:34 📁 来源:尧图网络
8086/8088 的 FLAGS 寄存器很多人学微机原理时都是考试前背一遍、考完就忘。我当年也是这样直到后来做底层调试被一个莫名其妙的进位错误卡了整整两天才回头把 FLAGS 里那 9 个标志位一个个抠清楚。这篇文章不打算照本宣科地列定义而是从这些位到底在什么场景下被谁改写、你该怎么读它这个角度把 FLAGS 讲透。无论你是正在啃《微机原理与接口技术》的学生还是写汇编、做嵌入式底层调试的工程师只要你和 8086/8088 的指令集打过交道这篇内容都能帮你把这块知识从背过变成用得上。1. 为什么 FLAGS 值得单独拎出来讲1.1 它不是一个普通寄存器而是 CPU 的状态面板8086/8088 内部有一组通用寄存器AX、BX、CX、DX 等它们的作用是存数据。FLAGS 不一样它只有 16 位却承担着完全不同的职责——记录上一条指令执行后 CPU 的身体状态。你可以把它理解成汽车仪表盘发动机转速、水温、油量这些信息本身不参与驾驶操作但司机必须随时看才能决定下一步踩油门还是刹车。FLAGS 里的每一个标志位本质上都是 CPU 在执行完某条指令后顺手留下的一个结论。比如做了一次加法结果是不是零有没有进位符号位是正还是负这些结论被写进 FLAGS后续的条件跳转指令JZ、JC、JS 等就靠读这些位来决定程序往哪走。没有 FLAGS程序就只会一条道走到黑分支、循环、比较全都无从谈起。1.2 16 位里真正有用的只有 9 位8086/8088 的 FLAGS 是 16 位但并不是每一位都有定义。实际有意义的是其中 9 位分成两大类状态标志Status Flags6 个由算术/逻辑运算指令根据结果自动设置反映运算结果的特征。控制标志Control Flags3 个由程序员通过专用指令手动设置用来控制 CPU 的某些行为。剩下的 7 位在 8086/8088 上是保留位或未定义读出来是什么值没有保证写进去也不影响 CPU 行为。这一点很关键——很多初学者看到 16 位就以为要全部记住其实抓住这 9 位就够了。下面这张表先把 9 个标志位的全貌列出来后面再逐个拆位名称类型作用一句话概括0CF 进位标志状态无符号运算最高位是否产生进位/借位2PF 奇偶标志状态结果低 8 位中 1 的个数是否为偶数4AF 辅助进位标志状态低 4 位是否向高 4 位产生进位/借位6ZF 零标志状态运算结果是否为 07SF 符号标志状态结果最高位是否为 1即是否为负8TF 陷阱标志控制是否进入单步调试模式9IF 中断允许标志控制是否响应可屏蔽中断10DF 方向标志控制串操作时地址是递增还是递减11OF 溢出标志状态有符号运算结果是否超出表示范围注意位 1、3、5、12、13、14、15 在 8086/8088 上没有定义不要依赖它们的值。1.3 状态标志和控制标志的本质区别这两类标志最容易混淆我用一句话区分状态标志是CPU 告诉你结果怎样控制标志是你告诉 CPU 该怎么做。状态标志CF、PF、AF、ZF、SF、OF你没法直接设置只能通过执行运算指令让 CPU 自动算出来。比如你想知道 AX 是不是零不能直接给 ZF 赋值而是执行CMP AX, 0或TEST AX, AX让 CPU 根据结果去设置 ZF。控制标志TF、IF、DF则相反它们不反映任何运算结果而是由你用 STI、CLI、STD、CLD、STC 这类指令主动控制用来改变 CPU 的工作模式。比如 DF 决定串操作时指针往哪个方向走这纯粹是你的意图跟运算结果无关。搞清这个区别后面看指令对 FLAGS 的影响时就不会乱。2. 六个状态标志运算结果到底留下了什么痕迹2.1 CF 进位标志无符号数的溢出报警器CF 记录的是无符号运算中最高位对 8 位运算就是 bit7对 16 位运算就是 bit15是否产生了进位或借位。加法时产生进位、减法时产生借位CF 都会被置 1。举个具体例子。8 位无符号数最大是 2550xFF如果算0xFF 0x011111 1111 0000 0001 ----------- 1 0000 0000 ← 最高位产生进位CF 1结果低 8 位为 0x00结果本应是 256但 8 位寄存器装不下只能留下 0x00同时 CF 置 1 告诉你进位丢了。这就是为什么多字节加法要靠 CF 把低位进位传递到高位——ADC指令带进位加法就是专门干这个的。减法同理0x00 - 0x01会向高位借位CF 1结果是 0xFF。这里有个容易搞反的点减法时 CF 表示借位而不是不够减的直觉。实际上 8086 的减法是用加法实现的加补码CF 反映的是这个加法过程有没有产生进位对减法来说就等价于借位。实操心得判断无符号数大小用 CF。CMP A, B之后如果 CF1说明 A B无符号意义下CF0 说明 A ≥ B。这是JB低于则跳转和JAE高于等于则跳转的底层依据。2.2 ZF 零标志最常用的一个位ZF 的逻辑最简单运算结果为 0 就置 1否则清 0。但它的使用频率是所有标志位里最高的因为判断相等本质上就是判断差是否为零。CMP A, B指令内部做的是 A - B但不保存结果只更新标志位。如果 A 和 B 相等差为 0ZF 就置 1。所以JZ为零跳转和JE相等跳转其实是同一条指令的两个名字机器码完全一样。循环控制也大量依赖 ZF。比如用LOOP指令做计数循环它内部会先对 CX 减 1然后判断 CX 是否为零——这个判断为零读的就是 ZF 的等价逻辑。虽然 LOOP 是专用指令但理解它背后的机制你就能明白为什么循环次数要放在 CX 里。2.3 SF 符号标志有符号数的正负号SF 直接复制运算结果的最高位。对 8 位结果就是 bit7对 16 位结果就是 bit15。在有符号数的语境下最高位是符号位所以 SF1 表示结果为负SF0 表示结果非负。这里必须强调有符号语境。同样一个二进制1000 0000当无符号数看是 128当有符号数看是 -128。SF 只反映最高位的值至于这个值代表正还是负取决于你把它当有符号还是无符号处理。CPU 不管这些它只负责把最高位搬进 SF。所以JS为负则跳转和JNS非负则跳转只在你有符号运算时才有意义。如果你在做无符号比较却用了 JS逻辑就错了。2.4 OF 溢出标志有符号数的越界警报OF 是最容易和 CF 搞混的一个。CF 管无符号溢出OF 管有符号溢出两者判断的是完全不同的东西。有符号 8 位数的范围是 -128 到 127。如果两个正数相加得到负数或者两个负数相加得到正数就说明结果超出了有符号表示范围OF 置 1。注意同号相加才可能溢出异号相加永远不会溢出因为异号相加的结果一定在两者之间不会越界。看个例子8 位有符号数0x7F 0x010111 1111 (127) 0000 0001 (1) ----------- 1000 0000 ← 结果最高位变 1看起来像 -128127 1 128但 8 位有符号数最大只能表示 127所以溢出了。此时 OF 1而 CF 0因为最高位没有产生进位。这个例子完美展示了 CF 和 OF 的区别同一个运算CF 看的是有没有进位OF 看的是结果有没有超出有符号范围。判断 OF 的硬件逻辑是最高位的进位输入与进位输出不同就置 OF。这个规则对加法和减法都适用是理解 OF 的钥匙。2.5 AF 辅助进位标志BCD 运算的专属工具AF 记录的是低 4 位半字节向高 4 位的进位或借位。它几乎只在 BCD二进制编码十进制运算里用得上。为什么需要它因为 BCD 码用 4 位表示一个十进制数字0-9两个 BCD 数相加时如果低 4 位的结果超过 9就需要加 6 修正才能得到正确的 BCD 结果。AF 就是用来判断要不要修正的信号。比如0x09 0x08BCD 的 9 80000 1001 (BCD 9) 0000 1000 (BCD 8) ----------- 0001 0001 ← 低 4 位是 1但实际应该是 17 的个位 7低 4 位 9817产生了进位AF 1。此时需要对低 4 位加 6 修正0001 0001 0110 0001 0111即 BCD 的 17。DAA加法后十进制调整指令就是靠读 AF 和 CF 来完成这个修正的。日常编程里 AF 用得很少但考试和 BCD 相关场景必考别忽略。2.6 PF 奇偶标志通信校验的历史遗留PF 反映运算结果低 8 位中 1 的个数偶数个 1 则 PF1奇数个则 PF0。注意它只看低 8 位即使你做的是 16 位运算。这个标志在早期的串行通信奇偶校验里有实际用途现在基本被硬件校验取代了。但在 8086 指令集里它依然存在JPE奇偶为偶则跳转和JPO奇偶为奇则跳转就是基于它。实操心得PF 只看低 8 位这一点经常被忽略。如果你做 16 位运算后想用 PF 判断奇偶得先明确它统计的只是低字节别拿它当整个 16 位结果的奇偶性用。3. 三个控制标志你如何指挥 CPU 的行为3.1 TF 陷阱标志单步调试的开关TF 置 1 时CPU 每执行完一条指令就产生一次内部中断类型 1把控制权交给调试程序。这就是单步调试的底层机制——调试器设置 TF程序走一步停一步你就能一条条观察寄存器和内存的变化。TF 平时必须为 0否则程序根本没法正常跑。只有调试器在需要单步时才临时置位执行完一条指令后由中断处理程序再置回去形成走一步停一下的循环。理解 TF 对写调试工具的人很重要对普通开发者来说知道单步调试靠的是 TF就够了。3.2 IF 中断允许标志可屏蔽中断的总闸IF 控制 CPU 是否响应外部可屏蔽中断INTR 引脚上的中断请求。IF1 允许响应IF0 屏蔽。这里的关键词是可屏蔽。8086 有两类外部中断可屏蔽中断INTR和不可屏蔽中断NMI。IF 只管前者NMI 不受 IF 影响任何时候都会响应。这个设计是有意为之——NMI 留给那些不能耽误的紧急事件比如电源故障IF 则让你在关键代码段比如修改共享数据里临时关中断避免被打断。设置 IF 的指令是STI置 1开中断和CLI清 0关中断。中断处理程序入口处硬件会自动清 IF防止中断嵌套失控返回时用IRET恢复。注意CLI 之后如果长时间不开中断系统的实时响应会变差。关中断的临界区要尽量短这是写底层代码的基本纪律。3.3 DF 方向标志串操作的指针方向DF 决定串操作指令MOVS、CMPS、SCAS、LODS、STOS执行时源指针 SI 和目的指针 DI 是自动递增还是递减。DF0 递增从低地址往高地址走DF1 递减。设置指令是CLD清 0递增和STD置 1递减。默认情况下 DF0所以大多数串操作是从前往后处理。什么时候需要 DF1当源和目的区域有重叠且你需要从高地址往低地址复制以避免数据被覆盖时。比如把一块内存往后挪从后往前复制才不会破坏还没搬走的数据。这是内存块移动的经典技巧REP MOVSB配合 DF 就能实现。4. 指令如何改写 FLAGS一张对照表胜过十页书4.1 哪些指令会动 FLAGS哪些不会这是初学者最容易踩的坑不是所有指令都会更新标志位。MOV、PUSH、POP、LEA这些数据传送指令完全不碰 FLAGS而ADD、SUB、CMP、TEST、AND、OR、XOR这些运算指令会更新大部分状态标志。为什么 MOV 不改标志因为标志位反映的是运算结果的特征而 MOV 只是搬运数据没有产生新的运算结果自然没有特征可反映。理解这一点你就不会写出用 MOV 之后判断 ZF这种错误代码。下面这张表列出常见指令对状态标志的影响改表示会更新—表示不影响0/1表示固定值指令CFZFSFOFAFPFMOV——————ADD/SUB改改改改改改ADC/SBB改改改改改改INC/DEC—改改改改改CMP改改改改改改AND/OR/XOR/TEST0改改0—改NOT——————NEG改改改改改改SHL/SHR改改改改—改MUL/IMUL改——改——DIV/IDIV——————4.2 INC 和 DEC 为什么不动 CF表里有个反直觉的点INC和DEC会更新 ZF、SF、OF、AF、PF唯独不碰 CF。这不是设计疏漏而是有意为之。原因在于 INC/DEC 经常被用作循环计数器而 CF 常常需要跨循环保持比如多字节加法里传递进位。如果 INC 顺手把 CF 清了程序员就得在每次循环里额外保存和恢复 CF非常麻烦。所以 Intel 让 INC/DEC 保留 CF让它们能安全地用在需要维持进位的循环里。这个细节考试爱考实际写汇编时也确实能省事。记住INC/DEC 不影响 CF。4.3 逻辑运算指令为什么把 CF 和 OF 清零AND、OR、XOR、TEST执行后CF 和 OF 一律清 0。这同样是设计约定不是运算结果决定的。逻辑运算按位操作没有进位和有符号溢出的概念所以 CF 和 OF 被强制清零避免残留的旧值误导后续判断。而 SF、ZF、PF 依然根据结果更新因为它们对逻辑运算有意义比如判断结果是否为零、最高位是什么。TEST指令特别值得说它做的是 AND 运算但不保存结果只更新标志位。所以TEST AX, AX是判断 AX 是否为零的经典写法比CMP AX, 0更高效少一次立即数比较。5. 从标志位到条件跳转分支逻辑是怎么落地的5.1 条件跳转指令和标志位的对应关系所有条件跳转指令Jcc本质上都是读某个标志位满足条件就改 CS:IP。搞清这个对应关系你写分支时就不用死记指令名而是从标志位反推该用哪条。指令跳转条件读的标志适用场景JZ/JE结果为零/相等ZF1相等判断JNZ/JNE结果非零/不等ZF0不等判断JC/JB有进位/低于CF1无符号小于JNC/JAE无进位/高于等于CF0无符号大于等于JS为负SF1有符号负JNS非负SF0有符号非负JO溢出OF1有符号溢出JNO无溢出OF0有符号未溢出JP/JPE奇偶为偶PF1奇偶校验JNP/JPO奇偶为奇PF0奇偶校验5.2 有符号和无符号比较别用错指令这是汇编里最经典的错误来源。同样是比较 A 和 B有符号和无符号要用不同的跳转指令无符号比较用JB低于、JAE高于等于、JBE低于等于、JA高于它们读的是 CF 和 ZF。有符号比较用JL小于、JGE大于等于、JLE小于等于、JG大于它们读的是 SF、OF 和 ZF 的组合。为什么有符号比较要同时看 SF 和 OF因为溢出会翻转符号位的含义。比如两个正数相加得到负数溢出此时 SF1 但实际结果应该是正的大数。JL这类指令内部做的是SF XOR OF的判断正是为了修正溢出带来的符号位失真。实操心得写比较跳转前先问自己我处理的是有符号还是无符号数。如果是地址、长度、计数这类天然非负的量用无符号指令如果是可能为负的数值用有符号指令。用错了不会报错但逻辑会悄悄出错非常难查。5.3 一个完整的比较分支实例假设要判断 AX 中的有符号数是否大于 100大于则跳转到 label_bigCMP AX, 100 ; AX - 100更新标志位 JG label_big ; 有符号大于则跳转读 SF/OF/ZF ; 否则继续执行 ... label_big: ...如果 AX 是无符号数同样的逻辑要改成CMP AX, 100 JA label_big ; 无符号高于则跳转读 CF/ZF两条代码只差一个字母但底层读的标志位完全不同。这就是为什么必须理解标志位而不是死记指令。6. 实操中那些文档不会告诉你的坑6.1 标志位会被下一条指令覆盖FLAGS 是上一条指令的结果它不保存历史。如果你执行了CMP AX, BX之后中间插了一条MOV CX, DX标志位不受影响MOV 不改标志但如果你插的是ADD CX, 1那 CMP 留下的标志就被覆盖了后面的跳转就基于 ADD 的结果逻辑全乱。所以比较指令和跳转指令之间不能插入任何会修改标志位的指令。这是汇编编程的铁律。调试时如果发现跳转行为诡异第一件事就是检查 CMP 和 Jcc 之间有没有夹带私货。6.2 PUSHF/POPF保存和恢复整个 FLAGS有时候你需要在修改标志位之前先保存现场用完再恢复。8086 提供了PUSHF把 FLAGS 压栈和POPF从栈弹出到 FLAGS这对指令。典型场景是中断处理程序进入时要保存当前 FLAGS处理完用IRET恢复。或者你在某段代码里临时改了 DF用完想还原成原来的值就可以先 PUSHF改完再 POPF。注意POPF 会直接覆盖整个 FLAGS包括 IF。如果你在关中断状态下 POPF 了一个 IF1 的值中断会立刻被打开。这在临界区里可能引发意外用的时候要清楚自己在恢复什么。6.3 标志位在调试器里怎么看用调试工具比如 DOSBox 的调试模式、或各类 8086 模拟器单步执行时通常能看到 FLAGS 的实时值。建议养成习惯每执行一条运算指令就瞄一眼 CF、ZF、OF 的变化对照指令的预期结果验证自己的理解。我当年就是靠这个方法把 CF 和 OF 的区别彻底搞明白的——盯着0x7F 0x01执行后 CF0、OF1 的那一刻比看十遍书都管用。6.4 常见误区清单最后把几个高频误区集中列一下方便自查误区一以为所有指令都改标志位。实际上 MOV、PUSH、POP、LEA、NOT 等都不改。误区二把 CF 和 OF 混为一谈。CF 管无符号OF 管有符号判断依据完全不同。误区三用有符号跳转指令处理无符号数或反之。逻辑会错且不报错。误区四以为 INC/DEC 会改 CF。它们不改这是有意设计。误区五在 CMP 和 Jcc 之间插入改标志的指令。标志被覆盖跳转失效。误区六以为 PF 统计整个 16 位结果。它只看低 8 位。把 FLAGS 这 9 个位吃透8086/8088 的指令集对你来说就不再是一堆孤立的助记符而是一套有内在逻辑的系统。运算指令负责产生状态跳转指令负责消费状态控制标志负责调节 CPU 行为——三者串起来就是程序流程控制的全部底层机制。我在实际调试里踩过的那些坑归根结底都是对这几个位理解不到位。希望这篇内容能帮你少走几年弯路。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →