MOV系列指令简单直接粗暴, 但程序员的工作内容一般都不是简单的数据传递, 一般都伴随着各种条件判断. 这一节介绍一些特殊的数据传递指令. 在介绍CMOV系列指令之前, 还需要介绍一些预备知识: 包括标志位寄存器, 以及在汇编中如何比较数值大小, 如何检查比较结果
EFLAGS寄存器
在详细介绍其它寄存器之前, 我们有必要先看一下这个特殊的寄存器. 你有没有想过, CPU执行数学运算的时候, 如果出现溢出, 进位, 借位等情况时, 计算机应当怎么处理? 毕竟寄存器的位宽是有限的, 在当前讨论的范畴中, 它不过最大才32位, 即便是等到以后我们接受x86_64汇编时, 寄存器也才64位, 能表达的数值范围始终是有限的. 总有意外要发生, 这个时候, 怎么办?
计算机中有一个特殊的寄存器, 就是用来标示运算结果的. 当数学运算出现溢出, 进位, 借位等状态时, 这个寄存器就会自动的把其中某个标志位置为1. 这就是所谓的"标志位寄存器". 在16位机时代, 标志位寄存器被命名为FLAGS Register
, 位宽同其它寄存器一样, 是16位. 进入32位时代后, 该寄存器的位宽也变成了32位, 同时名称变更为EFLAGS Register
, 相应的, 64位时代, 寄存器也有64位宽, 名称也变为RFLAGS Register
标志位寄存器并不仅仅能对数据运算的结果进行一些状态标示, 很多位还有其它方面的功能, 完整的标志位寄存器状态位说明表格如下:
标志位位置 | 取值掩码 | 惯用缩写叫法 | 标志位语义 | 标志位类别 |
---|---|---|---|---|
0 | 0x0001 | CF | 若ALU运算时发生进位或借位, 则置1 | 状态位 |
1 | 0x0002 | 未使用的位, 始终为1 | ||
2 | 0x0004 | PF | 奇偶检验位. 一般情况下只检验运算结果的最低位一字节. 即若运算结果的最低一字节的二进制表达中, 1的个数为偶数, 该标志位则置1. 否则置0 | 状态位 |
3 | 0x0008 | 未使用的位. 值未定义 | ||
4 | 0x0010 | AF | 辅助carry标志位. 当ALU运算时, 若数值的低四位(bit)在运算过程中要向其它高位借位或进位, 该标志位就置1. 一般的情况下, 我们是用不到这个标志位的, 它的存在主要是为了支持Binary-coded decimal算法 | 状态位 |
5 | 0x0020 | 未使用的位. 值未定义 | ||
6 | 0x0040 | ZF | 若ALU运算结果为0, 则该位置1 | 状态位 |
7 | 0x0080 | SF | 若ALU运算结果为负数, 则该位置1.其实本质上, 寄存器也好, CPU也好, 是不关心数值是有符号还是无符号的, 这个标志位只是始终保持与运算结果的最高位相同而已. 只是对于负数的二进制表达来说, 其最高位始终为1, 所以才有了, 若去处结果为负数, 该标志位置1的说法. | 状态位 |
8 | 0x0100 | TF | Trap flag (single step). 与支持调试相关 | 控制位 |
9 | 0x0200 | IF | Interrupt enable flag. 与硬件中断相关 | 控制位 |
10 | 0x0400 | DF | Direction flag. 在内存拷贝过程中, 该位主要控制拷贝的方向, 是先拷贝高地址, 还是先拷贝低地址. 对于src和dst内存区域有重叠时, 两种方式带来的结果是有差异的 | 控制位 |
11 | 0x0800 | OF | 若ALU运算时发生溢出, 则置1 | 状态位 |
12 - 13 | 0x3000 | IOPL | I/O privilege level (286+ only), | 系统标志位 |
always 1 on 8086 and 186 | ||||
14 | 0x4000 | NT | Nested task flag (286+ only), | 系统标志位 |
always 1 on 8086 and 186 | ||||
15 | 0x8000 | Reserved, | ||
always 1 on 8086 and 186, | ||||
always 0 on later models | ||||
32位寄存器上额外多出来的位 | ||||
16 | 0x0001 0000 | RF | Resume flag (386+ only) | 系统标志位 |
17 | 0x0002 0000 | VM | Virtual 8086 mode flag (386+ only) | 系统标志位 |
18 | 0x0004 0000 | AC | Alignment check (486SX+ only) | 系统标志位 |
19 | 0x0008 0000 | VIF | Virtual interrupt flag (Pentium+) | 系统标志位 |
20 | 0x0010 0000 | VIP | Virtual interrupt pending (Pentium+) | 系统标志位 |
21 | 0x0020 0000 | ID | Able to use CPUID instruction (Pentium+) | 系统标志位 |
22 | 0xFFC0 0000 | ID | Able to use CPUID instruction (Pentium+) | 系统标志位 |
23 - 31 | 0xFFCF 0000 | VAD | VAD Flag | 系统标志位 |
64位寄存器上额外多出来的位 | ||||
32-63 | 0xFFFF FFFF... | 未使用的位 | ||
...0000 0000 |
其实与数学运算相关的标志位仅有五个: CF, PF, ZF, SF, OF
这五个, 其实AF
也算, 但是很少用. 除非代码逻辑里需要引入BCD算法.
CMP指令
CMP
指令是一个用于比较两个数值的指令, 它的语法很简单: cmp B, A
, 比较大小其实是由ALU去执行A-B
运算, 比较的结果需要借助于其它的一些条件指令去使用. 或者去查看标志位寄存器.
需要注意的一点是:
在AT&T汇编中, 是右减左, 在Intel语法中, 是左减右!这简直就是AT&T最大的恶意
我们以AT&T语法为准, 来讲这个指令. 需要谨记的基本是下面两点:
- 被减数, 也就是AT&T语法中第二个操作数. 不能是立即数. 只能是内存值或寄存器值.
- 减数, 也就是AT&T语法中的第一个操作数. 可以是立即数, 可以是内存值, 也可以是寄存器值.
CMOV指令
C, 是conditional
的意思, 即为"带条件的". CMOV指令也是一系列的数据传递指令, 使用的语法和MOV是一毛一样的. 但它会检查CMP指令的执行结果. 若比较结果不符合预期, 则什么也不会做.
CMOV是一系列指令, 它的完全体由三部分组成: cmov<cc><x>
, 其中, <cc>
是条件, 是一个至三个英文字符, <x>
则指出了数据的位宽, 和mov
一样.
<cc>
一般有以下几种
含义 | |
---|---|
A | Above. > |
NA | Not Above. <= |
AE | Above or Equal. >= |
NAE | Not Above or Equal. < |
B | Below. < |
NB | Not Below. >= |
BE | Below or Equal. <= |
NBE | Not Below or Equal. > |
C | Carry. 发生进位或借位 |
NC | Not Carry. 没有发生进位或借位 |
E | Equal. == |
NE | Not Equal. != |
G | Greater. > |
NG | Not Greater. <= |
GE | Greater or Equal. >= |
NGE | Not Greater or Eqeual. < |
L | Less. < |
NL | Not Less. >= |
LE | Less or Equal. <= |
NLE | Not Less or Equal. > |
O | Overflow. 发生溢出 |
NO | Not Overflow. 没有发生溢出 |
示例程序: 获取数组中最大的数
下面是一个示例程序, 和上一节的示例程序比较相似. 不过个示例程序是通过CMP和CMOVA指令, 遍历数组的过程中, 挑出数组中最大的数输出.
.section .data
output:
.asciz "The largest value == %d\n"
values:
.int 11, 22, 141, 423, 53, 321, 545, 323, 546, 7 # 10个数的数组
.section .text
.globl _start
_start:
nop
movl values, %ebx # %ebx == values[0]
movl $1, %edi # %edi == 1
loop:
movl values(, %edi, 4), %eax # %eax == values[i]
cmp %ebx, %eax # if (%eax > %ebx)
cmova %eax, %ebx # { %ebx = %eax }
inc %edi # %edi++
cmp $10, %edi # if (%edi != 10)
jne loop # { goto loop }
pushl %ebx # printf("The largest value == %d\n", %ebx)
pushl $output
call printf
addl $8, %esp
pushl $0 # exit(0)
call exit
编译, 链接, 运行如下图所示: