想象你住在一座天气变化无常的城市。每天出门前,你会看一眼玄关的四块仪表:温度计(零上还是零下)、积水传感器(地面干不干)、水位计(排水渠有没有满溢)、气压计(读数有没有超出刻度)。这四块仪表合起来就是一座迷你气象站。你根据读数,对照墙上的“出行决策表”做决定:相等?不等?水位高?体感低于阈值?然后决定带不带伞、走不走隧道。 传统程序的写法是:走到路口才停下来思考——如果下雨就折返回去拿伞。这个“停下来思考”在 CPU 里叫分支判断,猜错方向就得倒车重来,整条流水线被冲刷。ARM 的做法很不一样:每条指令都可以预先盖一个“(如果下雨才执行)”的印章。出门前看一次仪表,路上所有动作都按预先盖好的章执行,不再纠结。这就是条件执行与标志位机制。 这篇文章带你从生活直觉出发,看懂四块仪表(NZCV 标志位)是什么、决策表(条件码)怎么查、印章(条件后缀)怎么盖、Thumb-2 的 IT 块怎么当“连续三天天气预报”用,以及工程师如何利用这套机制让端侧 AI 跑得更快、更省电。

岗位关联

学会条件执行与标志位机制,你就能读懂 ARM 汇编、看懂编译器生成的代码,在嵌入式 AI 推理、端侧部署和性能优化中精准定位瓶颈——这是底层调优的基本功。

目录
  1. 可视化演示
  2. 条件执行基础
  3. 标志位NZCV
  4. 条件码映射
  5. 条件后缀语法
  6. IT块机制
  7. 工程优化策略
  8. 硬件协同
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示ARM条件执行核心机制:标志位模式可视化NZCV四个标志位的设置与条件码映射关系,IT块模式展示Thumb-2中最多4条条件指令的谓词执行与流水线效率对比。

本节配有交互动效(进入视口后自动加载,约 3KB)。

NZCV标志位与条件码映射 NZCV标志位由CMP和算术指令设置,决定条件执行结果 N=负数 Z=零 C=进位 V=溢出,条件码映射标志位组合 EQ: Z==1, NE: Z==0, GE: N==V, LT: N!=V 条件后缀将分支转换为谓词执行,避免流水线气泡。

条件执行基础

出门前,你在计划清单上写“带伞”两个字,然后在旁边盖一个章:“(若下雨)”。天晴时,这个动作直接跳过,你不需要停下来思考“我要不要折返回去拿伞”——因为决定在出门那一刻就做好了。ARM 的条件执行,就是把这种“盖章”能力赋予每条指令。

在 ARM 世界里,这种“盖了章的计划”叫条件后缀。普通指令 ADD R0, R1 无条件执行;ADDNE R0, R1 则要求前一步比较结果“不相等”(Z=0)时才执行。这就是谓词执行:指令本身携带一个谓词,只有谓词为真才干活。

为什么这个机制重要?因为 CPU 内部是流水线工厂:传统“if 判断再跳转”会让工厂停下来等待判断结果,而条件执行只是让某道工序原地空转一拍,不需要整个工厂改变走向。工厂不用刹车,产量自然更高。

回到城市比喻:带伞出门的人,路上从容不迫;走到半路才折返取伞的人,每一步都在浪费时间。条件执行就是前者——提前决定,路上不折腾。

执行(IC)={1若 C 与 NZCV 匹配0若 C 与 NZCV 不匹配\displaystyle\text{执行}(I_C) =\begin{cases}1 & \text{若 } C \text{ 与 NZCV 匹配}\\[2pt]0 & \text{若 } C \text{ 与 NZCV 不匹配}\end{cases}
  1. 执行(I_C)带条件后缀 C 的指令 I,比如 ADDNE 里的 NE 就是条件。
  2. C 与 NZCV 匹配把条件码(如 EQ)和四个标志位 N/Z/C/V 的当前组合对照。
  3. 结果为 1条件是满足的,指令正常进入执行阶段。
  4. 结果为 0条件不满足,指令跳过,流水线里留下一个空泡。

标志位NZCV

气象站的四块仪表各有分工:N 是温度计,显示结果是零上还是零下(负数则亮);Z 是积水传感器,地面完全干爽(结果为零)才亮;C 是水位计,排水渠没溢(无符号进位)就亮;V 是气压计,读数超出刻度(带符号溢出)就会爆表。

CPU 怎么更新仪表?CMP(比较)指令就是气象员。它把两个数放上天平比一比,不记录谁重谁轻,只负责把仪表示数调好。比如 CMP A, B 会暗中计算 A−B,然后按结果点亮仪表:

哪些指令有权限碰仪表?CMP/CMN 是专职气象员;ADDS、SUBS 这类带 S 后缀的算术指令也会顺手更新仪表。而普通 ADD、MOV 只是路人,碰都不会碰仪表。

这就像家里的智能门锁——只有气象员和带 S 印章的人能改仪表读数,其他人看一眼可以,动手不行。后续条件指令读到的永远是最近一次“被授权更新”的读数。

CMP A,B  ⇒  {N=sign(A−B)Z=1  ⟺  A=BC=1  ⟺  A≥B(无符号)V=1  ⟺  带符号结果溢出\displaystyle\text{CMP } A,B \;\Rightarrow\;\begin{cases}N = \text{sign}(A-B)\\[2pt]Z = 1 \iff A = B\\[2pt]C = 1 \iff A \ge B \quad (\text{无符号})\\[2pt]V = 1 \iff \text{带符号结果溢出}\end{cases}
  1. CMP A,B比较指令,只更新标志位,不把差值写进寄存器。
  2. N结果的符号位。A−B 为负时置 1,相当于温度计指向零下。
  3. Z两数相等时置 1。差值正好为零,积水传感器触发。
  4. C无符号比较中 A≥B 时置 1。不够减说明要“借位”,水位计清 0。
  5. V带符号结果溢出时置 1。超出表示范围,气压计爆表。

条件码映射

仪表读数本身只是一串 0/1,怎么翻译成“带伞”还是“不带伞”?这就靠墙上的出行决策表——ARM 里叫条件码。

条件码是 16 种预定义的判断规则,每个规则把一个条件与标志位组合绑定。最常用的六组是:EQ/NE 看 Z 判断相等;CS/CC 看 C 判断无符号大小;GE/LT 看 N 与 V 的组合判断带符号大小;GT/LE 再叠加 Z 排除相等情况。

新手最容易懵的是:带符号比较为什么要 N==V?想象温度计因为气压计爆表而跳了刻度——只看温度计会误判。GE 的聪明之处是让温度计和气压计互相校正,得到真实方向。

决策表就贴在气象站旁边。CPU 不是人,它把整张表烧在译码电路里,对照一次比眨眼还快无数倍。你只需要在指令里写下条件码,剩下的查表交给硬件。

EQ:Z=1,NE:Z=0,CS:C=1,CC:C=0GE:N=V,LT:N≠V,GT:Z=0∧N=V,LE:Z=1∨N≠V\displaystyle\begin{aligned}\text{EQ}:Z{=}1,\quad & \text{NE}:Z{=}0,\quad\text{CS}:C{=}1,\quad \text{CC}:C{=}0\\[4pt]\text{GE}:N{=}V,\quad & \text{LT}:N{\ne}V,\quad\text{GT}:Z{=}0\wedge N{=}V,\quad \text{LE}:Z{=}1\vee N{\ne}V\end{aligned}
  1. EQ / NE直接看 Z。上次比较相等则 Z=1,EQ 成立;不相等则 Z=0,NE 成立。
  2. CS / CC看 C。无符号比较中 A≥B 时 C=1,CS(又叫 HS,高或同)成立;A<B 时 C=0,CC(又叫 LO,低)成立。
  3. GE / LT带符号比较中,N==V 说明“真实符号”为正,GE 成立;N≠V 说明溢出把符号弄反了,要取修正后的负,LT 成立。
  4. GT / LE在 GE/LT 基础上叠加 Z,把“相等”排除出去(GT)或包含进来(LE)。

条件后缀语法

给计划盖章的格式很有讲究:操作码 + 条件后缀 +(可选)S 印章。ADDEQS 就是 ADD(加法)+ EQ(相等才执行)+ S(顺便更新仪表)。这条指令翻译成人话:“如果上次比较相等,就把 R1 加 R2 存进 R0,并刷新仪表读数。”

这里有个关键分工:CMP 只更新仪表,不写结果;ADDS 既更新仪表又写结果。所以在代码里,CMP 后面经常跟着一串条件指令,像一个气象员播报完,大家各自按需行动。

但这引出依赖链的概念:条件指令要读仪表,而仪表由 CMP/ADDS 更新。如果在 CMP 之后、条件指令之前,另一条指令又改了仪表,后面的条件判断就读到“污染”数据了。工程师看汇编时,要找的就是这条链是否干净。

打个比方:你出门前看了一眼仪表,结果路上有人偷偷调了表,你到路口才发现早该拐弯。编译器优化的本质之一,就是把“偷调表”的指令挪走或合并,保证链条洁净。

ADDEQS R0,R1,R2  ≡  {R0←R1+R2若 Z=1跳过若 Z=0  ,并更新 NZCV\displaystyle\text{ADDEQS } R_0,R_1,R_2 \;\equiv\;\begin{cases}R_0 \leftarrow R_1+R_2 & \text{若 } Z=1\\[2pt]\text{跳过} & \text{若 } Z=0\end{cases}\;,\quad \text{并更新 NZCV}
  1. ADD基础操作码,加法。
  2. E条件后缀 EQ,要求 Z=1 才生效。
  3. S设置标志后缀,执行后更新 NZCV。
  4. 操作数Rd=R0, Rn=R1, Rm=R2,结果写回 R0。

IT块机制

气象站预报员决定给你一张“未来三天卡片”:今天出门看天气,明天、后天、大后天是否带伞,一次说清楚。Thumb-2 的 IT 指令就是这张卡片。

为什么需要它?因为在 Thumb-2 的 16 位指令里,每条指令都塞一个条件位太奢侈。IT 块的方案是:一条 IT 指令声明“接下来 1~4 条指令各自跟哪种天气”,后续指令自己就干净利落,不用再背条件。IT 指令的写法是 IT 加最多三个 T/E 字母再加条件码,例如 ITEE EQ:第一条按 EQ,第二条按 NE,第三条按 NE。

IT 块最多 4 条指令(首条 + 三个后缀位各对应一条)。这相当于天气预报最多预报“三天半”,再多就没地方写了。编译器在把 if-else 压进 IT 块之前,得先数一数判断体有没有超过 4 条。

有了这张卡片,旅行者出门前一次查完未来几天的安排,路上不用反复掏手机看天气——指令更少、跳转更少、流水线更顺。

\displaystyle\text{IT}_{xyz}\ \text{cond}:\quadI_1: \text{cond},\quadI_{k+1}:\begin{cases}\text{cond} & \text{若 }s_k=\text{T}\\[2pt]\neg \text{cond} & \text{若 }s_k=\text{E}\end{cases}\ (k=1,2,3)
  1. IT块头指令,声明后面跟着一个条件块。
  2. xyz三个后缀位,对应后三条指令的天气取向。
  3. I₁第一条必须是条件指令,它的条件就是 cond。
  4. s_k第 k 个后缀位是 T,则第 k+1 条沿用 cond;是 E,则取反 cond。

工程优化策略

精明的旅行者有一条原则:能不绕路就不绕路。编译器在 -O2 优化级别下会自动做这件事:把短的 if-else 翻译成条件执行,而不是生成跳转指令。因为它心里有一本成本账:

如果 N跳转 降到接近 0,第二项就没了,总时间几乎等于指令数本身。这就是条件执行在工程上的吸引力。

具体策略有三板斧:循环展开,把多次循环体的分支合并掉,让条件执行有更多发挥空间;条件转移融合,把多个 if 判断压缩成一次标志位计算;指令裁剪,用条件移动代替赋值分支,例如 if (x<0) x=0 在 ARM 上常被写成 CMP x,#0; MOVLT x,#0 两条指令。

省下的不只有时间,还有电量。每次跳转都要重新取指,条件执行让取指单元更“线性”,这对手机上的端侧 AI 推理尤其宝贵——省几个周期,就是省几毫秒;省几毫秒,就是省几分之一瓦。

\displaystyleT_{\text{总}} \approx N_{\text{指令}} \times \text{CPI} + N_{\text{跳转}} \times P_{\text{猜错}} \times k
  1. N指令 × CPI每条指令平均消耗的周期数,基础开销。
  2. N跳转代码里的分支指令数量。
  3. P猜错分支预测失败的估计概率。
  4. k每次预测失败冲刷流水线的周期损失。

硬件协同

把 CPU 想成一条工厂传送带:取指工位、译码工位、执行工位、写回工位一字排开。现代 ARM 流水线有 5~15 级,传送带越长,猜错一次方向要倒掉的半成品就越多。分支预测器就是那个猜方向的老师傅,可他再厉害也有失手的时候。

条件执行的高明之处在于:它根本不让传送带“猜方向”。指令带着自己的条件,译码后直接决定做或跳过,工厂永远知道下一步往哪走。数学上:

但这不意味着条件执行免费。每个条件指令都要额外译码,4 条条件指令挤在同一发射周期时,会占用执行宽度;若与加载指令的延重叠,还可能暴露读后写冒险。所以 ARM 工程师常说:能用条件执行处理“短而急”的判断,但不要用它处理“远而长”的跳转。

工厂管理也是如此:天气卡片适合处理“接下来几步走不走隧道”;可如果要绕整个城市一大圈,不如直接上高架(分支跳转)——那时老师傅的预测反而更划算。硬件与软件协同,永远在权衡中前进。

E[Pencond]=0,E[Penbranch]=pmiss×L\displaystyle\mathbb{E}[\text{Pen}_{\text{cond}}] = 0,\qquad\mathbb{E}[\text{Pen}_{\text{branch}}] = p_{\text{miss}} \times L
  1. E[Pen_cond]条件执行没有“猜”的过程,期望惩罚恒为 0。
  2. E[Pen_branch]分支方案的期望惩罚 = 预测失败概率 × 流水线深度。
  3. p_miss分支预测失败概率。
  4. L流水线级数,决定冲刷多少周期。

AI 应用场景

端侧AI推理的ReLU融合

阶段:模型部署与推理优化

解决的问题:传统 C 代码写 ReLU 激活函数 if (x > 0) y = x; else y = 0; 会被编译成分支。端侧模型经过量化后,卷积输出中约一半激活为零,分支预测器频频猜错,流水线不断被冲刷。用条件执行写成 CMP r0,#0; MOVLT r0,#0,就能无分支完成 ReLU,让流水线一路顺滑。

具体案例:某语音唤醒引擎在 Cortex-A55 上跑 INT8 卷积内核,把 ReLU 融进卷积尾部,用条件执行替代分支判断,端到端时延下降约 15%,功耗下降约 8%。

嵌入式语音AI的实时性保障

阶段:嵌入式AI模型部署

解决的问题:Cortex-M 系列没有复杂分支预测器,每条分支都实打实付出代价。短小的阈值判断、绝对值计算、限幅操作非常适合塞进 IT 块。用 ITEE 配合条件指令,可以把 2~3 条指令的判断体压成一个 4 指令块,彻底消除跳转,中断延迟更稳定。

具体案例:某 Cortex-M4 上的关键词识别(KWS)模型,将特征提取里的 abs(diff) 和阈值比较改为 IT 块后,推理时间抖动减少约 30%,同等电池下唤醒次数提升约 8%。

AI编译器后端的条件指令生成

阶段:AI编译器代码生成

解决的问题:TVM、LLVM 这类编译器在生成 ARM 后端代码时,需要把循环边界检查、尾部剩余元素处理等 if 逻辑翻译成机器指令。无条件生成分支会让小循环体膨胀;条件执行能把“边界内才执行”翻译成条件后缀,让主循环体和余量处理融合得更紧凑。

具体案例:某团队用 TVM 生成 ARMv7 NEON 卷积内核,把尾部 if (i < n) 处理换成 IT 块包裹的条件向量片段,核函数吞吐提升约 12%,代码体积减少约 6%。

Python 演示

python71 行
class FlagReg:    def __init__(self):        self.N = 0        self.Z = 0        self.C = 0        self.V = 0     def cmp_update(self, a, b):        # 用32位补码模拟CMP对NZCV的影响        res = a - b        self.N = 1 if (res & 0x80000000) else 0        self.Z = 1 if res == 0 else 0        self.C = 1 if (a & 0xFFFFFFFF) >= (b & 0xFFFFFFFF) else 0        sa = (a >> 31) & 1        sb = (b >> 31) & 1        sr = (res >> 31) & 1        self.V = 1 if (sa != sb and sr != sa) else 0  def cond_ok(code, f):    # 条件码到标志位组合的映射表    if code == 'EQ':        return f.Z == 1    if code == 'NE':        return f.Z == 0    if code == 'CS':        return f.C == 1    if code == 'CC':        return f.C == 0    if code == 'GE':        return f.N == f.V    if code == 'LT':        return f.N != f.V    return True  def run_conditional(flags, ops):    executed = 0    for op in ops:        code, _name = op        if cond_ok(code, flags):            executed += 1    return executed  def run_branch(flags, ops, penalty):    # 每个条件不满足的指令,在分支方案中对应一次可能的预测失败    stalls = 0    for op in ops:        code, _name = op        if not cond_ok(code, flags):            stalls += penalty    return stalls  def main():    f = FlagReg()    cases = [(5, 3), (3, 5), (4, 4), (-2, -3)]    program = [('EQ', 'ADD'), ('NE', 'ADD'), ('CS', 'MOV'), ('GE', 'SUB')]    total = 0    for a, b in cases:        f.cmp_update(a, b)        exe = run_conditional(f, program)        penalty = run_branch(f, program, 3)        total += penalty        print('a=%3d b=%3d exec=%d branch_penalty=%d' % (a, b, exe, penalty))    print('total branch penalty: %d' % total)  if __name__ == '__main__':    main()
目的:用 Python 模拟 ARM 的 CMP 标志位更新和条件码判断,对比条件执行方案与分支方案的开销差异。
  1. class FlagReg模拟 CPSR 里的 NZCV 四块仪表,属性 N/Z/C/V 对应四个标志位。
  2. def cmp_update(self, a, b)模拟 CMP 指令:计算 a−b 并更新四个标志。
  3. self.V = 1 if (sa != sb and sr != sa) else 0溢出检测:符号位变化且结果符号与操作数 a 不同,说明溢出。
  4. def cond_ok(code, f)模拟条件码映射表,EQ/NE/CS/GE 各自查标志位组合。
  5. def run_branch(flags, ops, penalty)每个条件不满足的指令,在分支方案中假设预测失败一次,累加惩罚周期。

常见误区

误区既然条件执行能避免分支预测失败,那我应该把所有 if-else 都改成条件后缀,性能一定起飞。

正解条件指令在译码阶段仍要判断标志位,条件不满足时虽然不干活,但指令本身已经占用了发射槽;条件块太长、依赖链太深时反而比分支慢。编译器只在“判断体短、分支易猜错”时才用条件执行。就像每件事都先看仪表再决定,会拖慢步伐;有时直接走固定路线反而更快。

为何常见:初学者只记得“条件执行免分支惩罚”,忘了译码宽度和发射槽同样是稀缺资源。

误区ADD、MOV 执行完,NZCV 就会自动反映结果。

正解只有 CMP、CMN 这类专门比较指令,以及带 S 后缀的 ADDS/SUBS/ANDS 才会更新标志位;普通 ADD 完全不影响标志。气象站的仪表只有气象员(CMP/S 指令)有权限调,路人(普通指令)路过不会碰仪表。

为何常见:看到“标志位”三个字,就觉得是所有运算的标配,没注意到 ARM 刻意区分了“更新仪表的权限”。

误区IT 块能减少分支,那我干脆把整段 if-else 都塞进 IT 块,越多越好。

正解Thumb-2 的 IT 块最多 4 条指令(含首条),且每条都必须是条件指令。超过 4 条编码格式就装不下了。天气预报最多预报“未来三天半”,没法预报一个月。

为何常见:记住了“最多 4 条”这个数字,但没有意识到这是编码格式的硬上限,不是建议值。

误区EQ 就是 Z 位本身,GE 就是 N 位等于 0,单个标志位直接对应一个条件码。

正解条件码是标志位的逻辑组合。GE 要判断 N==V 而不是只看 N;带符号比较必须先用 V 修正溢出,再看 N。决策表读的是“温度计+气压计联合判断”,不是只看温度计。

为何常见:初学时只记 EQ/NE 这种一对一的映射,遇到带符号比较 GE/LT/GT/LE 就套错公式。

面试题

Q1. 当一个加法运算的结果为零时,哪个标志位会被置 1?

单选题 · easy

  • A.N标志位
  • B.Z标志位← 正确答案
  • C.C标志位
  • D.V标志位

答案:B。Z(零标志)在结果为 0 时置 1。N 表示负数,C 表示无符号进位/借位,V 表示带符号溢出。就像积水传感器只在“地面完全干爽”时点亮。

Q2. 执行 CMP R0, R1 后,若 R0 == R1,下面哪个条件后缀的指令会执行?

单选题 · easy

  • A.ADDEQ← 正确答案
  • B.ADDNE
  • C.ADDCS
  • D.ADDGE

答案:A。CMP 比较相等时 Z=1,EQ 条件成立,所以 ADDEQ 会执行。NE 要求 Z=0,CS 看 C 标志,GE 看 N 与 V 的组合,都与“相等”无关。

Q3. 以下哪条指令一定会更新 CPSR 中的 NZCV 标志位?

单选题 · easy

  • A.ADD R0,R1,R2
  • B.ADDS R0,R1,R2← 正确答案
  • C.ADDNE R0,R1
  • D.MOV R0,R1

答案:B。只有带 S 后缀的算术指令 ADDS 才会更新标志位。普通 ADD、条件 ADDNE、MOV 都不碰标志。就像只有带 S 印章的气象员才有权限调仪表。

Q4. 在 Thumb-2 的 IT 块中,最多可以包含几条条件执行指令?

单选题 · mid

  • A.1条
  • B.2条
  • C.3条
  • D.4条← 正确答案

答案:D。IT 块最多 4 条指令,包括首条条件指令和最多 3 条由 T/E 后缀位控制的后续指令。这相当于天气预报最多预报“今天加未来三天”。

Q5. 带符号比较中,条件码 GE 为真的条件是?

单选题 · mid

  • A.N等于1
  • B.V等于1
  • C.N等于V← 正确答案
  • D.N不等于V

答案:C。GE(大于等于)要求 N==V,即温度计与气压计互相校正后得到“真实符号为正”。LT 则相反,要求 N≠V。

Q6. 以下哪些条件码适合无符号数比较?

多选题 · mid

  • A.CS和HS← 正确答案
  • B.CC和LO← 正确答案
  • C.GT和LT
  • D.GE和LE

答案:AB。无符号比较只看 C 标志:CS(高或同,HS)在 A≥B 时成立,CC(低,LO)在 A<B 时成立。GT/LT 和 GE/LE 是带符号比较用的条件码。

Q7. 关于 ADDS R0,R1,R2 执行后的标志位,下列说法正确的是?

多选题 · mid

  • A.Z=1表示结果为零← 正确答案
  • B.N=1表示结果为负← 正确答案
  • C.C=1表示必然溢出
  • D.V=0表示没有溢出← 正确答案

答案:ABD。Z=1 表示结果为零,N=1 表示结果为负,V=0 表示无溢出,这些说法都对。C=1 只表示无符号进位,与带符号溢出 V 是两回事,所以 C 选项错误。

Q8. 以下哪些情况更适合用条件执行替代分支?

多选题 · hard

  • A.判断体只有几条指令← 正确答案
  • B.跳转目标跨度很大
  • C.循环热点且分支易猜错← 正确答案
  • D.流水线已满载且无条件指令

答案:AC。判断体短时条件执行成本低;循环热点分支易猜错时,条件执行能把期望惩罚降为 0。跳转跨度大时条件后缀无法覆盖远距离目标,D 中流水线满载时条件指令可能挤占发射带宽,反而降低吞吐。

Q9. 若流水线深度为 K,分支预测失败一次的周期损失约为?

单选题 · hard

  • A.0个周期
  • B.1个周期
  • C.K个周期← 正确答案
  • D.2K个周期

答案:C。预测失败后,流水线里已取指、已译码的 K 级指令全部作废,需要冲刷后重新填充,所以损失约 K 个周期。条件执行因为不猜方向,这类损失恒为 0。

Q10. 执行 SUBS R0,R1,R2 且 R1=0x80000000、R2=1,则 V 标志为?

单选题 · extreme

  • A.V=0表示无溢出
  • B.V=1表示正溢出
  • C.V=1表示负溢出← 正确答案
  • D.V=0表示有溢出

答案:C。0x80000000 是 32 位有符号最小值,减 1 后结果应超出下界,回绕为正数 0x7FFFFFFF。负数减正数得到正数,符号翻转且溢出,V=1;因为方向是向下溢出,所以叫负溢出(下溢)。