汇编溢出

  二个异符号数相加,OF=0  
  二个同符号数相加,结果与加数符号不同,OF=1  
  二个同符号数相减,OF=0  

  二个异符号数相减,结果与减数符号相同,OF=1

==============================================

在 rflags 寄存器里的下面三个标志位记录溢出, 符号和进位状态:

  • OF(Overflow Flag)
  • SF(Sign Flag)
  • CF(Carry Flag)

1. 溢出产生的条件

我们看看下面这个式子,为了简单,作为例子我用 4 位数计算。

1.1 产生溢出

式子1:                                               式子2:
     0 1 1 1             ( 7 )                              0 0 1 1         ( 3 )
+    0 1 1 1             ( 7 )                        +     0 0 1 1         ( 3 )
-------------------------------                       ---------------------------
     1 1 1 0             ( -2 ) 溢出                         0 1 1 0         ( 6 ) 正确

上面的式子中,式子1 在 unsigned 数前提下 7 加 7 结果等于 14,这个结果看似正确。实际上它产生了 Overflow(溢出),结果超出了 4 位数能表达的范围,这是因为:当用来表达 signed(符号数)时,结果值 1110 却是 -2,那么 7 + 7 = -2 这显示是错误的,因此这里产生了溢出(超过了表达范围)。而式子2 是正确的。

在上面两个式子中,我们可以得到规律:

正数 + 正数 = 负数,就产生了溢出

接着再看下面的式子:

式子1:                                               式子2:
     1 1 0 0             ( -4 )                             1 1 0 0        ( -4 )
+    1 0 0 0             ( -8 )                      +      1 1 1 1        ( -1 )
-------------------------------                      ----------------------------
   1 0 1 0 0             (  4 ) 溢出                       1 1 0 1 1        ( -5 ) 正确
  --                                                      -- 
  进位                                                    进位

再来看看上面两个式子,式子1 在 singed 数的前提下 (-4) + (-8) = 4,结果很显然是错误的,因此这个式子也产生了溢出。而式子2 中, (-4) + (-1) = (-5) 这是正确的。

同样我们得到规律是:

负数 + 负数 = 正数,就产生了溢出

1.2 不产生溢出

       1 1 1 1                 ( -1 )
+      0 1 1 1                 ( 7 )
--------------------------------------
    1  0 1 1 0                 ( 6 ) 正确
   ---
   进位

最后再看看这个式子,在 signed 数的情况下:(-1) + 7 = 6 这个结果是正确的,因此这个式子不会产生溢出。

1.3 溢出总结

现在我们可以总结产生溢出的条件:

  1. 两个正数相加,如果结果为负数,就产生了溢出。
  2. 两个负数相加,如果结果为正数,就产生了溢出。

换个角度来说:

  1. 正数 - 负数 = 负数,就产生了溢出。
  2. 负数 - 正数 = 正数,就产生了溢出。

那么是什么情况不会产生溢出的?

  • 不同符号的两个数相加,不会产生溢出。

那么我们也可以得出同符号数相减,也不会产生溢出,例如:(-1) - (-1) = -1 + 1 = 0 这个式子是两个负数相减,等同于负数加正数,这不会产生溢出。

2. 产生进位与借位

上面 5 个式子中,有 3 个产生了进位情况,以 4 位数为例,当计算的结果高最位向上进一位时,就会产生进位情况,看看其中一个式子:

        1 1 0 0        ( -4 )
 +      1 1 1 1        ( -1 )
 ----------------------------
      1 1 0 1 1        ( -5 ) 正确
     -- 
    进位

计算结果向上进了 1 位,结果依然是正确的,rflags.CF 被置为 1,rflags.OF 被清 0,如果被作为 unsigned 数进行计算时:12 + 15 = 27,4 位数表达的结果为 11,这时需要得到正确结果,需借助 CF 标志。在这里 CF 被置位。

当然这里还会产生一个借位的情况,下面这个式子:

       0 1 0 1         ( 5 )
-      1 0 0 0         ( 8 )
----------------------------
     1 1 1 0 1         ( -3 )
    --
   借位

两个数相减,最高位不足减,向前借位,这种情况下也会产生 rflags.CF = 1

3. 符号位

数的最高位描述符号位,以 4 位数为例,最高位 Bit 3 是符号位,rflags.SF 会根据计算结果置相应的位,rflags.SF = 1 时表示为负数,而 rflags.SF = 0 则为正数。

4. 溢出,符号与进位的实际应用

它们较多应用在一些条件判断的场合,典型地根据两个数比较结果,做相应的处理。由数存在 unsigned 和 singed 因此在计算机中需要提供 unisgned 数和 singed 数的计算的解决方案。下面我们看看 OF, SFCF 标志位在 x86/x64 平台中条件判断中的应用。

4.1 signed 符号数的比较

首先我们来了解 signed 的条件判断,下面的代码:

signed int a;
signed int b;


if (a > b) {
    ... ...

} else if (a < b) {
    ... ...

} else if (a == b) {
   ... ...
}

那么我可以使用下面的指令来描述上面的 C 代码:

    mov eax, a
    cmp eax, b

    je equal                ; a == b ?
    jg great                ; a > b ?

    ... ...                 ; No! a < b

    jmp next

great:              
    ... ...                 ; Yes! a > b

    jmp next

equal:
    ... ...                 ; Yes! a == b

next:
    ... ...

在执行 cmp 指令后,processor 会设置 rflags 的标志位,cmp 指令实际上做减法处理,根据计算结果来更新 rflags 标志位。

4.1.1 等于

等于情况的判断比较容易理解,通过判断 rflags.ZF 标志,当 rflags.ZF = 1 时,表示 cmp 结果相等。使用指令 jzje 来进行判断,它们是同一个指令 opcode

4.1.2 大于

我们看看 processor 是如何判断符号数的大于以及小于的情况,假如有下面两个比较式子:

  • -1 > -2
  • 4 > -6

前面说过 cmp 指令做减法计算,我们来看看这个过程,同样以 4 位数作为例子

式子1:                                              式子2:

-1 - (-2):                                         4 - (-6):

     1 1 1 1    ( -1 )                                    0 1 0 0    ( 4 )
-    1 1 1 0    ( -2 )                               -    1 0 1 0    ( -6 )
-----------------------                             -----------------------
     0 0 0 1    ( 1 )                                     1 0 1 0    ( -6 ) 溢出
                                           

SF = 0                                                SF = 1
OF = 0                                                OF = 1
CF = 0                                                CF = 1
ZF = 0                                                ZF = 0

我们看到对于 -1 > -2 这个比较结果是:

  • SF = 0
  • OF = 0
  • CF = 0
  • ZF = 0

对于 4 > -6 这个比较结果是:

  • SF = 1
  • OF = 1
  • CF = 1
  • ZF = 0

显然这两个比较的式子都是 true 的,这个式子中也产生了 carry(也即是借进),它们相同的地方就是 SF = OF,因此我们可以判断:

SF = OF 时,比较结果是大于。

4.1.3 小于

现在我们来看看小于的情况是如何的?同样有下面的两个比较式子:

  • -1 > 2
  • -3 > 6

这两个式子我们知道,它们都是 false 的,同样我们来看看如何比较:

式子1:                                              式子2:

-1 - 2:                                             -3 - 6:

     1 1 1 1    ( -1 )                                    1 1 0 1    ( -3 )
-    0 0 1 0    ( 2 )                                -    0 1 1 0    ( 6 )
-----------------------                             -----------------------
    1 1 0 1    ( -3 )                                    0 1 1 1    ( 7 ) 溢出
                                                   
                                                  
SF = 1                                                SF = 0
OF = 0                                                OF = 1
CF = 0                                                CF = 0
ZF = 0                                                ZF = 0

式子 -1 > 2 的比较结果是:

  • SF = 1
  • OF = 0
  • CF = 0
  • ZF = 0

式子 -3 > 6 的比较结果是:

  • SF = 0
  • OF = 1
  • CF = 0
  • ZF = 0

这两个式子都是 false 的,它们相同之处就是 SF <> OF 我们这里得出来的结果是:

SF <> OF 时,比较结果是小于

4.1.4 signed 数的条件跳转指令

x86 提供了一系列的条件跳转指令来处理条件判断,下面是基本 signed 符号数的条件判断指令:

  • jg 大于
  • jge 大于或等于
  • jl 小于
  • jle 小于或等于

上面这 4 条是基本 singed 数判断指令,然而根据不同的表达词,又可以变化出多种助记符,例如:

  • jg 可以写成 jnle(不小于等于)
  • jge 可以写成 jnl(不小于)
  • jl 可以写成 jnge(不大于等于)
  • jle 可以写成 jng(不大于)

加上了一些否定修饰产生同样效果的指令,实际上这些对应的否定词指令是同一个 opcode 的不同表现形式,使用哪个,依据个人喜好。

这些指令正是使用了 rflags 寄存器的 SF, OF 以及 ZF 进行条件判断,例如:

  • jg 指令:当 ZF = 0 并且 SF = OF 时就跳转
  • jl 指令:当 SF <> OF 时就跳转,这里不需要判断(ZF = 0),仅需判断 SF <> OF 条件就足够了,因为:当 ZF = 1 时,必然 SF = OF
  • jge 指令:当 SF = OF 时就跳转,这里不需要判断(ZF = 1),仅需判断 SF = OF 条件就足够了(因为:无论 ZF 是否为 1 都满足大于或等于的条件
  • jle 指令:当 ZF = 1 或者 SF <> OF 时就跳转转,结果为 0 也满足条件(也就是当 ZF = 1(此时 SF = OF) 或者 SF <> OF 的情况下,都满足小于或等于的条件)

4.2 unsigned 无符号数的比较

对于无符号数来说,判断大小则简单多了,我们将上面的 C 代码改一改,a 和 b 定义为 unsigned 无符号:

unsigned int a;
unsigned int b;

if (a > b) {
    ... ...

} else if (a < b) {
   ... ...  
 
} else if (a == b) {
   ... ...
}

同样我可以用以下汇编指令描述为:

    mov eax, a
    cmp eax, b

    je equal              ; a == b ?
    ja above              ; a > b ?

below:
    ... ...               ; No! a < b
  
    jmp next

above:
    ... ...               ; Yes! a > b

    jmp next

equal:
    ... ...               ; Yes! a == b

next :
    ... ...

结构上是和 singed 数是一致的,只是使用了不同的指令。

4.2.1 大于

我们来看看 unsigned 数的大于情况,下面两个比较式子:

  • 12 > 3
  • 12 > 6

式子1:                                     式子2:

12 - 3:                                     12 - 6:

       1 1 0 0        ( 12 )                         1 1 0 0      ( 12 )
-      0 0 1 1        ( 3 )                 -        0 1 1 0      ( 6 )
----------------------------                ----------------------------
       1 0 0 1        ( 9 )                          0 1 1 0      ( 6 )

SF = 1                                      SF = 0
OF = 0                                      OF = 1
CF = 0                                      CF = 0
ZF = 0                                      ZF = 0

显然我们知道这两个式子都是 true 的,从上面的计算式子我们可以得到,对于 unsigned 数的比较:

当 CF = 0 时,比较结果大于

当 CF = 0 时表明计算结果没有产生借位,因此比较结果是大于。另外我们可以看出,当这个式子是 signe 符号数时,结果是小于。

4.2.2 小于

下面两个条件判断式子:

  • 7 > 8
  • 5 > 7

式子1:                                     式子2:

7 - 8 :                                     5 - 7:

       0 1 1 1        ( 7 )                         0 1 0 1      ( 5 )
-      1 0 0 0        ( 8 )                 -       0 1 1 1      ( 7 )
----------------------------                ----------------------------
       1 1 1 1        ( 15 )                        1 1 1 0      ( 14 )

SF = 1                                      SF = 1
OF = 1                                      OF = 0
CF = 1                                      CF = 1
ZF = 0                                      ZF = 0

由上面的计算式子可以看到:

当 CF = 1 时,比较结果是小于

同样当 CF = 1 时,表示被减数不足,需要借进,因此它是小于的。

4.2.3 unsigned 数的条件跳转指令

x86 提供了 4 个基本的 unsigned 数条件跳转指令:

  • ja 高于
  • jae 高于等于
  • jb 低于
  • jbe 低于等于

同样也可能加上否定修饰,变成以下几种形式:

  • ja 也可以为 jnbe(不低于等于)
  • jae 也可以为 jnb(不低于)
  • jb 也可以为 jnae (不高于等于)
  • jbe 也可以为 jna(不高于)

它们是依据 CFZF 进行判断:

  • ja 指令:当 CF = 0 且 ZF = 0 时跳转
  • jb 指令:当 CF = 1 且 ZF = 0 时跳转
  • jae 指令:当 ZF = 1 或者 CF = 0 时跳转
  • jbe 指令:当 ZF = 1 或者 CF = 1 时跳转

jb 指令的另一个形式可以为 jc(当 CF = 1 时跳转)。

5. eflags 标志位的设置

x86/x64 平台里的加减类运算中,指令会根据 unsigned 和 singed 结果设置 eflags 标志位,下面是 Intel 手册的一段话:

The SUB instruction performs integer subtraction. It evaluates the result for bothsigned and unsigned integer operands and sets the OF and CF flags to indicate anoverflow in the signed or unsigned result, respectively. The SF flag indicates the signof the signed result.

指令会对结果进行一个评估,评估根据操作数的 unsigned 和 signed 两方面。

那么,我们可以做出下面的推断,还是以 4 位数减法做为一个例子:

        0 1 1 1      ( 7 )
-       1 0 0 0      ( 8 )
--------------------------
        1 1 1 1      ( 15 )

这个二进制减法的结果的 1111B,sub 指令对这个结果进行评估:

  • 对于 unsigned operand 来说:eflags.CF = 1
  • 对于 singed operand 来说:正数减负数,结果为负数,结果溢出了,eflags.OF = 1efalgs.SF = 1

条件跳转指令可以对计算指令区分 unsigned 和 singed 两种情况:

mov eax, 0x70000000
mov ebx, 0x80000000

sub eax, ebx                                 ; 0x70000000 - 0x80000000

在例子中的 0x70000000 - 0x80000000 的减法计算中:

  • 对于 singed 计算结果,设置 eflags.OF = 1 和 eflags.SF = 1
  • 对于 unsinged 计算结果,设置 eflags.CF = 1

那么,我们可以使用两种条件跳转指令:

sub eax, ebx

jg .L1                                       ; signed 跳转指令

ja .L2                                       ; unsigned 跳转指令


  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值