【发布时间】:2021-03-14 21:23:53
【问题描述】:
我在《计算机系统-程序员的视角》中学习汇编代码,遇到了下面的例子:
在下面的C函数中,我们留下了操作的定义 操作不完整:
#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 retOP是什么操作?
后来,书中给出了如下答案:
运算符是'/'。我们看到这是一个除以幂的例子 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 的预期答案。相反,我们将 15 添加到 -12 得到 3(又名 00000011),然后我们可以将其右移 4 位得到 00000000,也就是十进制形式的 0 的正确答案。
以上解释正确吗?还是在谈到如何以及为什么使用偏见时我错过了标记?
更新——显然我正在使用的书中的示例汇编代码不正确。这是正确的程序集:
arith:
testw %di, %di
leal 15(%rdi), %eax
cmovns %edi, %eax
sarw $4, %ax
ret
我关于为什么需要偏见的更大问题仍然存在。是不是因为在没有先添加偏差的情况下移动负数会产生我提到的错误结果?
【问题讨论】:
-
那个例子坏了,返回值寄存器是 RAX 而不是 RBX。
short也绝对不会使用 64 位操作数大小。 godbolt.org/z/fq6Mf6 显示实际的 asm 输出。这是来自 CS:APP 的全球版吗?已知有一些不完善的实践问题,由非作者修改。请参阅CS:APP example uses idivq with two operands? 了解详细信息。 -
有道理,我也对
%rbx的使用感到有些困惑。我将修复问题中的示例,以便更清楚。 -
查看我对之前评论的编辑,这不是唯一的问题。可能最好保持 CS:APP 代码不变,或者完全删除它并使用实际的 GCC 输出。除了这本书对 asm 的描述半途而废。您的问题仍然可以回答,正确的实现仍然涉及 +=15 向上舍入(向零)而不是向 -inf,以实现有符号除法语义。
-
只有当你定义它是正确的时候它才是正确的:)这只是C如何进行除法和shift如何进行它之间的区别。如果您希望余数始终为非负数,则移位是正确的。
-
@PeterCordes 是的,它来自 CS:APP 的第三全球版。感谢您的提醒。 RE:“它也绝对不会简称为 64 位操作数大小”——你的意思是示例中的指令后缀应该是“w”而不是“q”吗?
标签: assembly x86-64 bit-shift integer-division