之前的答案都没有完全解决我自己的困惑,所以我想添加自己的答案。
我缺少的是lea 操作处理括号的使用与mov 不同。
想想 C。假设我有一个 long 数组,我称之为 array。现在表达式array[i] 执行解引用,从内存地址array + i * sizeof(long) [1] 处加载值。
另一方面,考虑表达式&array[i]。这仍然包含子表达式array[i],但不执行解引用! array[i] 的含义已经改变。它不再意味着执行服从,而是充当一种规范,告诉&我们正在寻找什么内存地址。如果您愿意,您也可以将& 视为“取消”取消引用。
因为这两个用例在很多方面都很相似,所以它们共享语法 array[i],但 & 的存在与否会改变该语法的解释方式。没有&,它是一个取消引用,实际上是从数组中读取的。对于&,它不是。 array + i * sizeof(long) 的值仍在计算中,但并未取消引用。
mov 和lea 的情况非常相似。使用mov,会发生lea 不会发生的取消引用。尽管在两者中都使用了括号。例如,movq (%r8), %r9 和 leaq (%r8), %r9。对于mov,这些括号表示“取消引用”;与lea,他们没有。这类似于array[i] 在没有& 时仅表示“取消引用”。
举个例子。
考虑代码
movq (%rdi, %rsi, 8), %rbp
这会将内存位置%rdi + %rsi * 8 的值加载到寄存器%rbp 中。即:获取寄存器%rdi中的值和寄存器%rsi中的值。将后者乘以 8,然后将其与前者相加。 找到该位置的值并将其放入寄存器%rbp。
这段代码对应于C行x = array[i];,其中array变成%rdi,i变成%rsi,x变成%rbp。 8 是数组中包含的数据类型的长度。
现在考虑使用lea的类似代码:
leaq (%rdi, %rsi, 8), %rbp
正如movq 的使用对应于解引用,leaq 的使用在这里对应不 解引用。这一行汇编对应于C行x = &array[i];。回想一下& 将array[i] 的含义从取消引用更改为简单地指定位置。同样,leaq 的使用将 (%rdi, %rsi, 8) 的含义从取消引用更改为指定位置。
这行代码的语义如下:获取寄存器%rdi中的值和寄存器%rsi中的值。将后者乘以 8,然后将其与前者相加。将此值放入寄存器%rbp。不涉及内存加载,仅涉及算术运算 [2]。
请注意,我对leaq 和movq 的描述之间的唯一区别是movq 进行了取消引用,而leaq 没有。其实写leaq的描述,我基本上是复制+粘贴movq的描述,然后去掉“在这个位置找值”。
总结一下:movq 与 leaq 比较棘手,因为它们处理括号的使用方式不同,如 (%rsi) 和 (%rdi, %rsi, 8)。在movq(以及除lea 之外的所有其他指令)中,这些括号表示真正的取消引用,而在leaq 中,它们不是并且纯粹是方便的语法。
[1] 我说过当array 是long 的数组时,表达式array[i] 会从地址array + i * sizeof(long) 加载值。这是真的,但有一个微妙之处需要解决。如果我写 C 代码
long x = array[5];
这不和打字一样
long x = *(array + 5 * sizeof(long));
似乎应该根据我之前的说法,其实不是。
发生的事情是 C 指针加法有一个技巧。假设我有一个指针p 指向T 类型的值。表达式p + i 确实不是的意思是“p 的位置加上i 字节”。相反,表达式p + i 实际上 的意思是“p 的位置加上i * sizeof(T) 字节”。
这样做的方便之处在于,要获得“下一个值”,我们只需编写 p + 1 而不是 p + 1 * sizeof(T)。
这意味着C代码long x = array[5];实际上等价于
long x = *(array + 5)
因为 C 会自动将 5 乘以 sizeof(long)。
那么在这个 StackOverflow 问题的上下文中,这一切有什么关系?这意味着当我说“地址array + i * sizeof(long)”时,我确实不是意味着将“array + i * sizeof(long)”解释为C 表达式。我自己乘以 sizeof(long) 是为了让我的答案更明确,但请理解,因此,这个表达式不应被解读为 C。就像使用 C 语法的普通数学一样。
[2] 旁注:因为所有lea 所做的都是算术运算,所以它的参数实际上不必引用有效地址。出于这个原因,它通常用于对可能不打算取消引用的值执行纯算术运算。例如,cc 和 -O2 优化转换
long f(long x) {
return x * 5;
}
进入以下(不相关的行已删除):
f:
leaq (%rdi, %rdi, 4), %rax # set %rax to %rdi + %rdi * 4
ret