CF 和 OF 标志位
看汇编语言时看到,标志寄存器中 CF
标志位表示无符号数运算时是否向最高有效位外的更高位产生进位或借位,而 OF
标志位表示有符号数运算时是否产生溢出。这里存在两个疑问:
- 对于
CPU
来说,它并不区分处理的是无符号数还是有符号数,那什么时候设置CF
,什么时候设置OF
呢 CF
表示进位时也是一种溢出,能否和OF
共用一个
CF
首先来看 CF
进位的例子,这里我们以8位无符号数为例,其最大值为255,那么计算 255 + 1
则会产生进位。可以通过一段简单的汇编代码进行验证:
1 | .section .text |
在上述代码中,al
是一个8位寄存器,是 eax
寄存器的低8位,这里首先将255放到 al
寄存器内,然后对 al
寄存器中的值加1并放回到 al
寄存器中,即实现 255 +1
的运算。
最后的 int $0x80
中的 int
表示 interrupt
,即中断,当发生一个中断时会有一个与之对应的中断处理程序来处理,这里的 $0x80
就是声明由哪个中断处理程序处理,在 Linux
中,$0x80
对应的是操作系统内核,用于发起一个系统调用,而具体发起哪个系统调用则由 eax
中的值决定,这就是 movl $1, %eax
的作用,1对应的系统调用是 exit
,用于退出程序,而程序退出时会伴有一个状态码,这个状态码的值来自于 ebx
,也就是 movl $0, %ebx
的作用,这里使用0来表示程序正常退出。
接下来我们借助 gdb
来观察程序运行时 CF
的值的变化。首先将上述代码保存为 demo.s
后进行编译:
1 | as demo.s -o demo.o -gstabs+ |
这里的 -gstabs+
表示生成机器码时同时生成调试信息,如果没有这个选项后续 gdb
加载时会提示 (No debugging symbols found in ./demo)
。
然后进行链接:
1 | ld demo.o -o demo |
这个时候就可以通过 gdb
加载生成的可执行文件:
1 | gdb ./demo |
然后输入 break 4
在代码第四行设置一个断点,即 mov $255, %al
处,最后输入 run
开始调试执行:
此时可输入 layout reg
来观察各寄存器内的值,我们需要关注的是 eflags
寄存器,它展示了哪些标志位生效了:
或者通过执行 info registers eflags
来查看 eflags
的值:
1 | (gdb) info registers eflags |
目前只有一个 IF
标志位,它用于表示是否响应中断。
接着,输入 next
来执行当前断点所在处的指令,可以看到,执行后 rax
寄存器内的值变成了255(rax
是64位 CPU
下的一个通用寄存器,32位 CPU
下对应为 eax
):
再输入一次 next
来执行加法运算,此时 rax
中的值变为了0(实际的二进制结果应该是100000000,因为 al
寄存器最多只能表示8位,所以最高位的1无法表示,最终结果为0),eflags
中出现了 CF
标志位,说明发生了进位:
rax
中的值为0也说明了加法运算后产生的进位并不会体现在比参与运算的寄存器位数更多的寄存器中,否则 rax
中的值应该是256。
再来看借位,将程序稍加修改执行一个 1 - 2
的运算:
1 | .section .text |
最后 rax
中的值为255(存在高位借位的情况下最后的二进制结果为11111111,解释为无符号数为255),eflags
中同样出现了 CF
标志位。
所以,CF
的标记取决于两个二进制数的运算是否产生进位或借位。
OF
有符号数的溢出分两种情况,一种是运算结果应该是正数却返回负数,另一种是运算结果应该是负数却返回正数。
首先来看两个正数运算得到负数的例子,同样对代码稍加修改实现 127 + 1
的运算:
1 | .section .text |
最后 rax
中的值为128(对应二进制表示为10000000,以有符号数的角度来看,其值为-128,即两个正数相加得到一个负数),eflags
中出现了 OF
标志位,说明发生了溢出:
从有符号数的角度来看,参与运算的两个数的符号位都是0,相加后符号位却是1,所以 OF
设置为1。
再来看两个负数运算得到正数的例子,再次对代码稍加修改实现 -128 - 1
的运算,-128的二进制补码表示为10000000,即无符号数角度下的128,-1的二进制补码表示为11111111,即无符号数角度下的255:
1 | .section .text |
最后 rax
中的值为127(对应二进制表示为01111111,以有符号数的角度来看,其值为127,即两个负数相加得到一个正数),eflags
中出现了 OF
标志位,说明发生了溢出:
从有符号数的角度来看,参与运算的两个数的符号位都是1,相加后符号位却是0,所以 OF
设置为1。
所以,OF
的标记取决于运算结果的符号位是否发生变化,这里的变化指的是两个相同符号位的数的运算结果是一个不同符号位的数。
比较
注意到前面有符号数 -128 - 1
运算的例子中,最后 CF
和 OF
都被设置为了1,说明 CF
和 OF
并不是互斥的关系,在这个例子中即发生了进位又发生了符号位的变更,也就是说如果满足了设置 CF
的条件,那么 CF
就是1,如果满足了设置 OF
的条件,那么 OF
就是1。因此,回到文章开头的问题,CPU
并不是去判断该设置 CF
还是 OF
,而是只要条件满足就会设置对应的标志位,而具体应该关注哪个标志位,则交由编译器去判断,因为对 CPU
而言它处理的只是比特运算,只有编译器知道当前的运算数是无符号数还是有符号数。
另外,CF
和 OF
也不能合二为一,无法相互替代,例如两个无符号数相加 CF
有可能是0,但是 OF
却是1,如 127 + 1
;两个有符号数相加 OF
有可能是0,但是 CF
却是1,如 -1 - 1
。也有可能 CF
和 OF
都是1,如有符号数运算 -128 - 1
。