当前位置: 首页 > news >正文

汇编语言:标志寄存器

目录
  • flag​ 寄存器
  • 标志位
    • 零标志位 ZF
    • 奇偶标志位 PF
    • 符号标志位 SF
    • 进位/借位标志 CF
    • 溢出标志 OF
    • x86 中的标志位
  • 进位/借位的加/减法
    • adc 指令
    • sbb 指令
  • flag 寄存器的访问
  • 参考资料

flag​ 寄存器

在 8086CPU 中还有一类不同于ax、cs​ 这类整体存放数据的 flag​ 寄存器,这类寄存器按位起作用,每一位都记录特定的状态信息。它们存储的信息通常被称为程序状态字(PSW)。8086 CPU 的 flag 结构有 16 位,其中 0、2、4、6、7、8、9、10、11 这 9 位是具有实际含义的。
image
这 9 个标志位大致可以分为两类:

类型 标志位 作用
反映运算结果 ZF、PF、SF、CF、OF 记录结果是零、奇偶、正负、进位、溢出
控制 CPU 行为 DF、IF、TF 控制串操作方向、中断响应等

在 Debug 里,flag 不会显示成一串 0000010101010101B,而是按位拆成单个标记。在 Debug 中执行 r 命令,会看到类似这一行:

NV UP EI PL NZ NA PO NC

8 组字母的每一组对应一个标志位,前一个是值为 0 的标记,后一个是值为 1 的标记:

标志位 值为 1(真) 值为 0(假) 含义
OF OV NV 溢出
DF DN UP 方向(减/增)
IF EI DI 中断允许
SF NG PL 符号(负/正)
ZF ZR NZ
AF AC NA 辅助进位
PF PE PO 奇偶
CF CY NC 进位

标志位

零标志位 ZF

flag 的第 6 位为 ZF(Zero Flag,零标志位),记录用于指令执行结果是否为 0,若结果为 0 则 ZF = 1,若结果不为 0 则 ZF = 0。例如以下两条指令执行后 AX = 0,CPU 发现结果是 0 就把 ZF 置 1。

mov ax, 1
sub ax, 1

image

mov 等传送指令不影响标志位,只有,例如 add、sub、mul、div、and、or 等的运算类指令才会改变 ZF。

奇偶标志位 PF

flag 第 2 位是 PF(Parity Flag,奇偶标志位),用于记录结果的所有二进制位中“1 的个数”是否为偶数。若 1 的个数为偶数则 PF = 1,若 1 的个数为奇数则 PF = 0

mov al, 1      ; AL = 00000001B,1 个 1 → PF = 0
add al, 1      ; AL = 00000010B,1 个 1 → PF = 0
add al, 1      ; AL = 00000011B,2 个 1 → PF = 1

image

PF 主要用于早期通信中的奇偶校验,现在更多是作为 CPU 行为的副产品存在。

符号标志位 SF

flag 第 7 位是 SF(Sign Flag,符号标志位),它用于记录结果的最高位(符号位)是否为 1。若结果为负(最高位为 1)则 SF = 1,若结果非负(最高位为 0)则 SF = 0。由于计算机使用补码表示有符号数据,因此同一串二进制既可以是正数,也可以是负数。例如如下指令执行后 AL = 10000010B(82H):

mov al, 10000001B    ; 81H
add al, 1

如果当作无符号数,则 129(81H) + 1 = 130(82H),SF 的值没有意义;如果当作有符号数(补码),则是 -127 + 1 = -126,最高位是 1、SF = 1。
image

例如如下指令执行后 AL = 10000010B(81H):

mov al, 10000001B    ; 81H
add al, 01111111B    ; 7fH

执行后结果为 0、sf = 0,表示如果指令进行的是有符号运算,则结果为非负。
image

SF 是 CPU 对有符号数运算结果的指示,它总是会被设置,但只有在把数据当有符号数看时 SF 才有逻辑意义。

进位/借位标志 CF

CF(Carry Flag,进位/借位标志)位于 flag 的第 0 位,它记录的是无符号数运算中,最高有效位向更高位的进位或借位。8086 中的一个 8 位寄存器最多能表示 0~255,一个 16 位寄存器最多能表示 0~65535。做加法时如果结果超过了这个范围,CPU 会将这个进位值记录在 CF 标志上,而不是直接丢弃它。例如对于以下指令:

mov al, 98H     ; AL = 98H = 152(无符号)
add al, al      ; AL = 98H + 98H = 130H

从 8 位视角看 AL 里只剩下 30H,但 CPU 知道把那个溢出去的 1记在 CF 中,执行后得到 AL = 30HCF = 1
image

减法同理,此时 CF 的含义变成了借位:

mov al, 97H     ; AL = 97H = 151(无符号)
sub al, 98H     ; 151 - 152,不够减,向更高位借 1

CPU 实际算的是 197H - 98H,借位被记录在 CF 中,执行后得到 AL = FFHCF = 1
image

溢出标志 OF

OF(Overflow Flag,溢出标志)位于 flag 的第 11 位,它记录的是有符号数运算结果是否超出了机器能表示的范围。机器能表示的范围指的是补码的范围,8 位补码是 -128 ~ 127,16 位补码是 -32768 ~ 32767,一旦结果超出这些范围 OF 就会被置 1。补码是用固定位数表示正负的,位数不够时就会产生溢出,例如以下指令:

mov al, 63        ; AL = 99(有符号)
add al, 62        ; 98 + 99 = 197

计算结果 197 超出了 8 位有符号数的上限 127,因此 AL = 0C5H(-59 的补码)、OF = 1
image

以下指令计算 -16 + (-120):

mov al, 0F0H      ; AL = -16(有符号)
add al, 88H       ; -16 + (-120) = -136

得数 -136 超出了 8 位有符号数的下限 -128,因此 AL = 78H(120 的补码),OF = 1

image

CF 与 OF 的功能有一定相似之处,但它们描述的是**同一个运算中两种不同的“正确性”,之间没有任何关系。

标志位 服务于 关心什么 不关心什么
CF 无符号数 有没有进位 / 借位 结果是正还是负
OF 有符号数 结果有没有超出补码范围 有没有进位

例如以下 3 组指令,有进位时,OF 可以 1 也可以 0;有溢出时,CF 可以 1 也可以 0,它们是独立的。

指令 无符号 CF 有符号 OF
mov al,98 / add al,99 152+99,无进位 0 98+99 溢出 1
mov al,0F0H / add al,88H 240+136,有进位 1 -16-120 溢出 1
mov al,0F0H / add al,78H 240+120,有进位 1 -16+120 不溢出 0

而 CPU 不知道你要把数据当有符号还是无符号,所以要同时记录 CF 和 OF。执行完运算指令后,CPU 通过 flag 寄存器就同时知道如下信息:

  • 如果这是无符号加法,有没有进位?(CF)
  • 如果这是有符号加法,有没有溢出?(OF)
  • 结果是正还是负?(SF)
  • 结果是不是 0?(ZF)

至于程序里到底用的是哪一种,那是编程时要考虑的事。CPU 只负责把可能的事实都记下来,放在 flag 里待汇编程序使用。

x86 中的标志位

在现代 x86 架构中,EFLAGS/RFLAGS 寄存器继承了 8086 flag 的所有位,如 CFZFSFOF 依然存在,在此基础上还进行了大幅扩展。编译器在生成代码时,会充分利用这些标志位进行分支优化,例如 C 语言的分支结构:

if (a == 0) { ... }

编译后通常会变成如下指令,其中的 je 就是依赖 ZF 标志位。

cmp eax, 0
je  .L1

编译器在生成代码时会严格区分 CF 和 OF,在底层它们对应不同的条件跳转指令。例如以下这段代码编译器可能基于 CF 判断溢出:

unsigned int a, b, c;
c = a + b;

以下这段代码编译器可能基于 OF 判断溢出:

int a, b, c;
c = a + b;

进位/借位的加/减法

adc 指令

adc 的全称是 add with carry,它的功能是利用 CF 上记录的进位值实现带进位的加法。该指令的格式为 adc 操作对象1, 操作对象2,其计算结果为:操作对象1 + 操作对象2 + CF,多出来的 + CF 就是上一次运算留下的进位(或借位)。CF 不是 adc 自己算出来的,而是前面那条指令“留给它的”。例如考虑如下指令:

mov ax, 2
mov bx, 1
sub bx, ax      ; cf = 1
adc ax, 1       ; ax = ax + 1 + CF

指令 sub bx, ax 发生了借位,将 CF 设置为 1,然后 adc ax, 1 把 CF 当成额外加数一起算进去,最终 ax = 2 + 1 + 1 = 4
image

8086 的寄存器只有 16 位,一次最多处理 16 位数据。但现实中的数往往更大,比如 1EF000H + 201000H,已经超过 16 位了。此时 CPU 的解决方案类似于列竖式,第一步先加低位 98 + 83 = 11B,产生进位 1;第二步,接着再加高位 01 + 01 + 进位 1 = 03

  01 98
+ 01 83
-------03 1B

adc 的效果就类似该过程的第二步,使用该指令就可以实现大数加法。例如计算 32 位加法 1EF000H + 201000H,用 ax 保存结果的高 16 位,bx 保存低 16 位,指令如下:

mov ax, 001EH
mov bx, 0F000H
add bx, 1000H
adc ax, 0020H

指令首先使用 add 对低 16 位相加,此时产生进位设置 CF = 1。然后使用 adc 将高 16 位和进位相加,得到计算结果
1774310-20260812212321960-1735608186

48 位加法同理,例如计算 1EF0001000H + 2010001EF0H,令结果为 ax = 最高 16 位,bx = 次高 16 位,cx = 低 16 位,指令如下:

mov ax, 001EH
mov bx, 0F000H
mov cx, 1000H
add cx, 1EF0H    ; 低16位相加
adc bx, 1000H    ; 次高16位 + 进位
adc ax, 0020H    ; 最高16位 + 进位

也就是最低 16 位用 add,之后每一层都用 adc 把上一层的进位加上。
image

同理,考虑一个 128 位加法子程序。因为第一次加法不需要任何进位,必须从 CF = 0 的状态开始,因此使用 sub ax, ax 把 CF 清零。使用 inc si 而不是 add si, 2 的原因在于 inc 指令不影响 CF,但是用 add si, 2 可能会修改 CF,从而破坏上一层 adc 留下的进位,这样程序的计算结果可能错误。loop 不影响 CF,所以可以令进位传递到下一轮。

; 功能:128 位加法
; 参数:
;   ds:si 指向第一个数(8 个字,低地址到低字节)
;   ds:di 指向第二个数
; 结果:存储在第一个数位置add128:push axpush cxpush sipush disub ax, ax        ; 将 CF 清零mov cx, 8
s:mov ax, [si]adc ax, [di]mov [si], axinc siinc siinc diinc diloop spop dipop sipop cxpop axret

sbb 指令

sbbsubtract with borrow,和 adc 是对称的,它的格式和功能是:sbb 操作对象1, 操作对象2 = 操作对象1 − 操作对象2 − CF。例如计算 32 位减法 003E1000H - 00202000H,结果令 ax = 高 16 位,bx = 低 16 位,指令如下:

mov bx, 1000H
mov ax, 003EH
sub bx, 2000H
sbb ax, 0020H

sbb 的使用方式和 adc 一样,只是把加法换成减法,进位换成借位。指令中的 sub 令低 16 位相减并设置 CF = 1(借位),然后再用 sbb 指令实现高 16 位 - 借位。
image

现代 CPU 中 adc / sbb 依然存在,而且在大整数库(GMP、OpenSSL)中发挥了重要作用。现代 x86 还引入了 adcx / adox 这类指令,可以同时使用 CF 和 OF 两个标志位,用于高性能加密运算。

flag 寄存器的访问

adcsbb 等指令都是在使用 flag 寄存器,但如果想知道 flag 里的具体数值,或者想人为设置一个 flag 状态,需要使用 8086 提供的两条专用的指令:

指令 功能
pushf 把整个 flag 寄存器压入栈
popf 从栈顶弹出一个字,写入 flag 寄存器

例如对于如下一段指令:

mov ax, 0
push ax
popf
mov ax, 0fff0h
add ax, 0010h
pushf
pop ax
and al, 11000101B
and ah, 00001000B

push ax0000H 压栈,popf0000H 写入 flag,此时 flag 所有位被清零。
image

然后执行加法 FFF0H + 0010H = 10000H,结果为 AX = 0000H。
image

由于结果为 0 所以 ZF = 1;产生了从最高位的进位,因此 CF = 1;低 8 位全为 0,1 的个数为 0,此时为偶数所以 PF = 1;最高位为 0 是非负数,所以 SF = 0;这个加法为 -16 + 16 = 0,没溢出产生溢出所以 OF = 0。此时 flag 中关键位的状态是:

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
标志 - - - - OF DF IF TF SF ZF - AF - PF - CF
0 0 0 0 0 0 0 0 0 1 0 1 0 1 0 1

此时 flag = 0000 0000 0101 0101B = 0055H,然后 pushf 把当前 flag (0055H) 压栈,pop ax0055H 弹入 AX。最后使用 and 指令,对于低 8 位 AL01010101B AND 11000101B = 01000101B = 45H;对于高 8 位 AH00H AND 08H = 00H;最终结果为 AX = 0045H
image

参考资料

《汇编语言(第四版)》,王爽 编著,清华大学出版社

http://www.jsqmd.com/news/1407852/

相关文章:

  • 企业管理书籍推荐:企业越发展,越需要扎实的管理能力
  • 精密微型电机哪家好:真正靠谱的厂家都会主动提供这三份文件 - 推客
  • 2026推荐:香河城区酥皮肉饼实力门店怎么选?这份本地老饕指南请收好 - 装修教育财税推荐2026
  • 2026 年现阶段石狮靠谱的不锈钢列管式换热器实力厂家全面解析与选购指南,用错它,工厂每月多花几万电费?车间设备升级必看这玩意儿-曦豪不锈钢冷凝器 - 行业鉴选官
  • 浙江有实力的光学镜架直销厂商,源头工厂为何更值得信赖 - 装修教育财税推荐2026
  • 异地报名无人机维修培训,是否统一收费且包含住宿保障 - 湖南阳光技术
  • 2026年华东滚塑成型制品定制 爱迪威滚塑设备专业承接 - 起跑123
  • 2026年值得信赖的靠谱的南阳大客车出租公司怎么选择哪家好,看服务流程与车辆管理是否到位 - 海棠依旧大
  • 2026年8月南通D证三轮摩托车驾驶员培训中心有哪些测评,主流培训模式中心分析 - 海棠依旧大
  • 2026年严选:行业内上海景观泡沫雕塑厂家推荐 - 海棠依旧大
  • 2026年想找靠谱精密喷砂机厂家 不妨了解浙江派挺科技 - 起跑123
  • 管理学书籍推荐:真正值得认真读的一本管理经典!
  • 2026年第3季度重庆市线下实体配镜不线上办理流程:预约准备与办理步骤、完整流程和资料清单 - 小校长
  • 2026年浙江选购全自动喷漆线 可重点参考劲嵩设备品质 - 起跑123
  • 2026年8月评价好的不锈钢管实力厂家哪里有卖,角钢/H型钢/工字钢/涂塑钢管/楼承板/耐磨钢板,不锈钢管实力厂家哪家好 - 企业权威推荐大使
  • 2026 年当下,香洲诚信的校车推广公司联系电话,想让娃上学路更省心?这靠谱的校园通勤方式你还没试过?-抖策盈网络科技 - 企业推荐管【认证】
  • 2026 年宁波可靠的溺水打捞企业有哪些,你永远想不到,这门冷门手艺里藏着多少不为人知的门道。-魔都打捞队 - 行业推荐官-2
  • 2026 年威县比较好的篮球场围网供应厂家联系电话,你家楼下的空地,居然还藏着这么容易被忽略的健身安全细节?-江欧丝网 - 行业鉴选官
  • 2026年宁波高端家具定制哪家好,来雅贝克看看 - 起跑123
  • 2026 年至今,惠州专业的化工厂拆除回收公司找哪家,拆除旧厂还能赚大钱?背后那项不为人知的操作你得知道 - 企业推荐管【认证】
  • 2026 年更新:顺义专业的变压器回收厂家推荐,拆解旧厂设备时意外发现的闲置金属疙瘩,原来它还能换不少钱?-再塑变压器回收 - 企业信息推荐-2
  • 2026年成都女士定制假发哪家好 实地探访优质品牌 - 起跑123
  • 2026年天车式五轴加工中心厂家哪家好 德凯数控深耕领域 - 起跑123
  • 2026 年新发布:卢湾口碑好的1600KVA变压器供货商哪家靠谱,20年工厂用电没省过钱,直到换了这台硬家伙竟能省下半度电? - 企业信息推荐-2
  • 2026推荐:房山有实力的挤塑板厂家怎么选?认准这几点不踩坑 - 装修教育财税推荐2026
  • 2026年浙江选靠谱浮标,友特塑料容器值得参考 - 起跑123
  • 2026年第3季度重庆市开车防眩光眼镜去哪配攻略指南:适用场景与完整流程、准备事项和选择要点 - 小校长
  • 2026年宁波马尔圆度仪哪家好 选型要点全面科普 - 起跑123
  • 2026年找宁波园区软件首版次申报机构选宁波磐业科技服务 - 起跑123
  • 2026年8月市面上阿城水果包装厂家怎么选择推荐,常规纸箱、彩印礼盒、贴纸标签及配套辅材厂家分析 - 海棠依旧大