3.6.2 访问条件码
3.6.2 访问条件码
条件码通常不会直接读取,常用的使用方法有三种:1)可以根据条件码的某种组合,将一个字节设置为 0 或者 1,2)可以条件跳转到程序的某个其他的部分,3)可以有条件地传送数据。对于第一种情况,图 3-14 中描述的指令根据条件码的某种组合,将一个字节设置为 0 或者 1。我们将这一整类指令称为 SET 指令;它们之间的区别就在于它们考虑的条件码的组合是什么,这些指令名字的不同后缀指明了它们所考虑的条件码的组合。这些指令的后缀表示不同的条件而不是操作数大小,了解这一点很重要。例如,指令 setl 和 setb 表示“小于时设置(set less)”和“低于时设置(set below)”,而不是“设置长字(set long word)”和“设置字节(set byte)”。
一条 SET 指令的目的操作数是低位单字节寄存器元素(图 3-2)之一,或是一个字节的内存位置,指令会将这个字节设置成 0 或者 1。为了得到一个 32 位或 64 位结果,我们必须对高位清零。一个计算 C 语言表达式 a < b 的典型指令序列如下所示,这里 a 和 b 都是 long 类型:
| 指令 | 同义名 | 效果 | 设置条件 |
|---|---|---|---|
sete D |
setz |
D ← ZF | 相等/零 |
setne D |
setnz |
D ← ~ZF | 不等/非零 |
sets D |
D ← SF | 负数 | |
setns D |
D ← ~SF | 非负数 | |
setg D |
setnle |
D ← ~(SF ^ OF) & ~ZF | 大于(有符号 >) |
setge D |
setnl |
D ← ~(SF ^ OF) | 大于等于(有符号 >=) |
setl D |
setnge |
D ← SF ^ OF | 小于(有符号 <) |
setle D |
setng |
D ← (SF ^ OF) | ZF | 小于等于(有符号 <=) |
seta D |
setnbe |
D ← ~CF & ~ZF | 超过(无符号 >) |
setae D |
setnb |
D ← ~CF | 超过或相等(无符号 >=) |
setb D |
setnae |
D ← CF | 低于(无符号 <) |
setbe D |
setna |
D ← CF | ZF | 低于或相等(无符号 <=) |
图 3-14 SET 指令。每条指令根据条件码的某种组合,将一个字节设置为 0 或者 1。有些指令有“同义名”,也就是同一条机器指令有别的名字
int comp(data_t a, data_t b)
a in %rdi, b in %rsi
1 comp:
2 cmpq %rsi, %rdi Compare a:b
3 setl %al Set low-order byte of %eax to 0 or 1
4 movzbl %al, %eax Clear rest of %eax (and rest of %rax)
5 ret
注意 cmpq 指令的比较顺序(第 2 行)。虽然参数列出的顺序先是 %rsi(b)再是 %rdi(a),实际上比较的是 a 和 b。还要记得,正如在 3.4.2 节中讨论过的那样,movzbl 指令不仅会把 %eax 的高 3 个字节清零,还会把整个寄存器 %rax 的高 4 个字节都清零。
某些底层的机器指令可能有多个名字,我们称之为“同义名(synonym)”。比如说,
setg(表示“设置大于”)和setnle(表示“设置不小于等于”)指的就是同一条机器指令。编译器和反汇编器会随意决定使用哪个名字。
虽然所有的算术和逻辑操作都会设置条件码,但是各个 SET 命令的描述都适用的情况是:执行比较指令,根据计算 t = a - b 设置条件码。更具体地说,假设 a、b 和 t 分别是变量 a、b 和 t 的补码形式表示的整数,因此 t = a -tw b,这里 w 取决于 a 和 b 的大小。
来看 sete 的情况,即“当相等时设置(set when equal)”指令。当 a = b 时,会得到 t = 0,因此零标志置位就表示相等。类似地,考虑用 setl,即“当小于时设置(set when less)”指令,测试一个有符号比较。当没有发生溢出时(OF 设置为 0 就表明无溢出),我们有当 a -tw b < 0 时 a < b,将 SF 设置为 1 即指明这一点,而当 a -tw b >= 0 时 a >= b,由 SF 设置为 0 指明。另一方面,当发生溢出时,我们有当 a -tw b > 0(负溢出)时 a < b,而当 a -tw b < 0(正溢出)时 a > b。当 a = b 时,不会有溢出。因此,当 OF 被设置为 1 时,当且仅当 SF 被设置为 0,有 a < b。将这些情况组合起来,溢出和符号位的 EXCLUSIVE-OR 提供了 a < b 是否为真的测试。其他的有符号比较测试基于 SF ^ OF 和 ZF 的其他组合。
对于无符号比较的测试,现在设 a 和 b 是变量 a 和 b 的无符号形式表示的整数。在执行计算 t = a - b 中,当 a - b < 0 时,CMP 指令会设置进位标志,因而无符号比较使用的是进位标志和零标志的组合。
注意到机器代码如何区分有符号和无符号值是很重要的。同 C 语言不同,机器代码不会将每个程序值都和一个数据类型联系起来。相反,大多数情况下,机器代码对于有符号和无符号两种情况都使用一样的指令,这是因为许多算术运算对无符号和补码算术都有一样的位级行为。有些情况需要用不同的指令来处理有符号和无符号操作,例如,使用不同版本的右移、除法和乘法指令,以及不同的条件码组合。
练习题 3.13 考虑下列的 C 语言代码:
int comp(data_t a, data_t b) {
return a COMP b;
}它给出了参数 a 和 b 之间比较的一般形式,这里,参数的数据类型 data_t(通过 typedef)被声明为表 3-1 中列出的某种整数类型,可以是有符号的也可以是无符号的,COMP 通过 #define 来定义。
假设 a 在 %rdi 中某个部分,b 在 %rsi 中某个部分。对于下面每个指令序列,确定哪种数据类型 data_t 和比较 COMP 会导致编译器产生这样的代码。(可能有多个正确答案,请列出所有的正确答案。)
A. cmpl %esi, %edi
setl %al
B. cmpw %si, %di
setge %al
C. cmpb %sil, %dil
setbe %al
D. cmpq %rsi, %rdi
setne %al
练习题 3.14 考虑下面的 C 语言代码:
int test(data_t a) {
return a TEST 0;
}它给出了参数 a 和 0 之间比较的一般形式,这里,我们可以用 typedef 来声明 data_t,从而设置参数的数据类型,用 #define 来声明 TEST,从而设置比较的类型。对于下面每个指令序列,确定哪种数据类型 data_t 和比较 TEST 会导致编译器产生这样的代码。(可能有多个正确答案,请列出所有的正确答案。)
A. testq %rdi, %rdi
setge %al
B. testw %di, %di
sete %al
C. testb %dil, %dil
seta %al
D. testl %edi, %edi
setne %al