我正在“计算机系统-程序员的视角”中学习汇编代码,并遇到以下示例:
在以下C函数中,我们保留了操作OP的定义不完整:
#define OP /* Unknown operator */ short arith(short x) { return x OP 16; }
编译时,gcc生成以下汇编代码:
arith: leaq 15(%rdi), %rax testq %rdi, %rdi cmovns %rdi, %rax sarq $4, %rax ret
OP是什么操作?
后来,这本书给出了以下答案:
运算符为“ /”。我们看到这是一个右移除以4的幂的示例(请参阅第2.3.7节)。在以k = 4移位之前,当股息为负时,我们必须添加(2 ^ k)− 1 = 15的偏差。
我知道编译器在这里使用优化,创建一个等于x + 15的临时变量,如果x小于零,则有条件地将该变量重新设置为x。我想知道的是为什么首先需要使用偏见。如果代码没有象前3个汇编步骤那样会发生什么呢?
sarq $4, %rax
ret
我认为答案是,我们需要摆脱负数的二进制补码符号位,才能得出正确的答案为零。例如,如果x = -12(即11110100),并且我们想除以4,则在不先添加偏置的情况下向右移4位将等于11111111(或十进制形式的-1),这不是预期的答案我希望将-12除以16所得的0之分。取而代之的是,我们将-12加15以得到3(又称00000011),然后我们可以将其右移4位以得到00000000,也就是0的正确答案十进制形式。
以上解释正确吗?还是在偏见的使用方式和原因方面我错过了标记?
更新-显然我正在使用的书中的示例汇编代码是不正确的。这是正确的程序集:
arith:
testw %di, %di
leal 15(%rdi), %eax
cmovns %edi, %eax
sarw $4, %ax
ret
关于为什么需要偏见的更大问题仍然存在。是因为在不先加上偏见的情况下移位负数会产生我提到的错误结果吗?
使用二进制补码表示的右算术移位负值执行整数除以2的幂,并朝着负无穷大舍入。这不是C中整数除法的语义,在整数除法中必须向0舍入。
为了实现16的有符号除法,编译器在分子为负数时将分子偏移15,然后将算术右移4:
arith:
testw %di, %di // testing numerator
leal 15(%rdi), %eax // computing numerator+15 into %eax, flags unchanged
cmovns %edi, %eax // conditional move depending if numerator was negative
sarw $4, %ax // arithmetic right shift by 4 positions
ret
该代码等效于此:
short div16(short num) {
return (num < 0 ? num + 15 : num) >> 4;
}