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
还有一些其他更加简单的情况,比如说一个有符号的-1
和1
相加,那么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计算0xffffffff
和0x80000000
相加得到的的结果应为0x17fffffff
(即十进制的-2147483649
),但这就超出了int32所能表示的范围,故ecx
是即得到的结果为0x7fffffff
(即十进制的2147483647
),这就发生了负溢出。因此在执行完add
指令后,OF标志位被强制置为1
.