掘金 阅读 ( ) • 2024-03-30 11:07

108001170_p0.jpg

CF标志位

Case1: 无符号数加法产生进位

unsigned int a = 0xff'ff'ff'ff;
00007FF7C56F182B  mov         dword ptr [a],0FFFFFFFFh  
unsigned int b = 1;
00007FF7C56F1832  mov         dword ptr [b],1  
unsigned int c = a + b;
00007FF7C56F1839  mov         eax,dword ptr [b]  
00007FF7C56F183C  mov         ecx,dword ptr [a]  
00007FF7C56F183F  add         ecx,eax  
00007FF7C56F1841  mov         eax,ecx  
00007FF7C56F1843  mov         dword ptr [c],eax

在执行add ecx,eax这条指令后,由于产生了进位,CF标志位(Visual Studio中为CY)被强制置为1.

Case2: 无符号数减法产生借位

首先来看一下直观的理解:

在处理两个无符号数的减法运算时,CF标志位作为借位标志(Borrow Flag)来使用。CF标志位用于指示在无符号算术运算中是否发生了借位或者说是否需要借位。

具体到减法操作:当执行减法操作时,如果被减数小于减数,即无法直接完成减法而需要借位时,CF会被置为1。这表示发生了借位,或者说结果需要从更高位借位才能表示。反之,CF会被置为0,这表示操作可以顺利完成,无需任何借位。

例子如下:

unsigned int a = 1;
00007FF7363318BB  mov         dword ptr [a],64h  
unsigned int b = 2;
00007FF7363318C2  mov         dword ptr [b],0C8h  
unsigned int c = a - b;
00007FF7363318C9  mov         eax,dword ptr [b]  
00007FF7363318CC  mov         ecx,dword ptr [a]  
00007FF7363318CF  sub         ecx,eax  
00007FF7363318D1  mov         eax,ecx  
00007FF7363318D3  mov         dword ptr [c],eax

在这个例子中,虽然站在有符号数的角度来看1 - 2这个运算并没有任何问题,我们将会得到-1的补码。但从无符号数的角度来看,由于被减数1小于减数2,在形式上我们并不能直接完成减法操作,需要从更高的位"借位"。在这种情况下,CF标志位会被置为1,表示发生了向高位的借位。

下面我们再站在ALU的角度来思考一下这个问题。

首先,我们知道计算机执行a - b运算的本质是计算a + (~b + 1)

下面,我们还是通过具体的例子来说明这个过程中发生了什么(为了方便说明问题,假设通用寄存器宽度为4bit):

  • a < b情形:假设a=1, b=2,则a - b = 0b0001 + 0b1101 + 1 = 0b1111 = -1。在这个运算过程中并没有发生二进制数向高位的进位,CF被置为1。
  • a ≥ b情形:假设a=2, b=1,则a - b = 0b0010 + 0b1110 + 1 = 0b0001 = 1。在这个运算过程中发生了二进制数向高位的进位,CF被置为0。

可见,如果我们从ALU的视角来思考问题的话,会发现在进行无符号数减法时,CF的取值与ALU底层执行的运算是否产生进位之间的关系,正好与无符号数加法相反。

当然必须说明的是,笔者在此介绍这种理解方式,只是因为曾在网课资料上看到有人如此讲授。实事求是地讲,这种理解方式显然没有前面提到的"向高位借位"的理解方式那般直观和方便记忆。

ZF标志位

ZF标志位(Visual Studio中为ZR)用于标识最近的操作得出的结果是否为0

例如,还是在这个无符号数加法进位的例子中,由于执行add ecx,eax发生了进位,用于储存计算结果的ecx寄存器中的值变为0x00000000,ZF标志位也随之被强制置为1.

unsigned int a = 0xff'ff'ff'ff;
00007FF7C56F182B  mov         dword ptr [a],0FFFFFFFFh  
unsigned int b = 1;
00007FF7C56F1832  mov         dword ptr [b],1  
unsigned int c = a + b;
00007FF7C56F1839  mov         eax,dword ptr [b]  
00007FF7C56F183C  mov         ecx,dword ptr [a]  
00007FF7C56F183F  add         ecx,eax  
00007FF7C56F1841  mov         eax,ecx  
00007FF7C56F1843  mov         dword ptr [c],eax

还有一些其他更加简单的情况,比如说一个有符号的-11相加,那么ZF标志位也会被置为1.

OF标志位

Case1: 正溢出(Positive Overflow)

当两个正数运算(不一定是加法)的结果超出了该数据类型可表示的最大正数范围时,发生正溢出。

例子如下:

    int a = 0x7f'ff'ff'ff;
00007FF7AEA3182B  mov         dword ptr [a],7FFFFFFFh  
    int b = 1;
00007FF7AEA31832  mov         dword ptr [b],1  
    int c = a + b;
00007FF7AEA31839  mov         eax,dword ptr [b]  
00007FF7AEA3183C  mov         ecx,dword ptr [a]  
00007FF7AEA3183F  add         ecx,eax  
00007FF7AEA31841  mov         eax,ecx  
00007FF7AEA31843  mov         dword ptr [c],eax

在这个例子中,变量a保存的已是int32数据类型所能表示的最大正数值0x7fffffff,因此再将其加1就会发生正溢出。具体地,add得到结果为0x80000000,即int32数据类型所能表示的最小负数值的补码形式,同时OF标志位(Visual Studio中为OV)将被强制置为1.

Case2: 负溢出(Negative Overflow)

当两个负数运算的结果超出了该数据类型可表示的最小负数范围时,发生负溢出。

例子如下:

    int a = 0xff'ff'ff'ff;  // 补码形式的-1
00007FF62F911F9B  mov         dword ptr [a],0FFFFFFFFh  
    int b = 0x80'00'00'00;  // 补码表示为-2147483648
00007FF62F911FA2  mov         dword ptr [b],80000000h
    int c = a + b;
00007FF62F911FA9  mov         eax,dword ptr [b]  
00007FF62F911FAC  mov         ecx,dword ptr [a]  
00007FF62F911FAF  add         ecx,eax  
00007FF62F911FB1  mov         eax,ecx  
00007FF62F911FB3  mov         dword ptr [c],eax

我们期望ALU计算0xffffffff0x80000000相加得到的的结果应为0x17fffffff(即十进制的-2147483649),但这就超出了int32所能表示的范围,故ecx是即得到的结果为0x7fffffff(即十进制的2147483647),这就发生了负溢出。因此在执行完add指令后,OF标志位被强制置为1.