x86-64 在寄存器中传递参数的顺序

x86-64 order of passing parameters in registers

对x86-64环境下的参数传递过程很好奇,写了一段代码。

//a.c
extern int shared;
int main(){
    int a=100;
    swap(&a, &shared);
}
//b.c
int shared=1;
void swap(int* a, int* b){
    *a ^= *b ^= *a ^= *b;
}

我使用以下命令编译了两个文件: gcc -c -fno-stack-protector a.c b.c 然后我objdump -d a.o查看a.o的反汇编代码

Disassembly of section .text:

0000000000000000 <main>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   48 83 ec 10             sub    [=11=]x10,%rsp
   8:   c7 45 fc 64 00 00 00    movl   [=11=]x64,-0x4(%rbp)
   f:   48 8d 45 fc             lea    -0x4(%rbp),%rax
  13:   be 00 00 00 00          mov    [=11=]x0,%esi
  18:   48 89 c7                mov    %rax,%rdi
  1b:   b8 00 00 00 00          mov    [=11=]x0,%eax
  20:   e8 00 00 00 00          callq  25 <main+0x25>
  25:   b8 00 00 00 00          mov    [=11=]x0,%eax
  2a:   c9                      leaveq 
  2b:   c3                      retq

由于我的工作环境是Ubuntu 16.04 x86-64,我觉得传递参数的顺序比较难理解

在我看来,这里的默认调用约定是fastcall,因此参数从右向左传递。

我从 x86-64 System V ABI 手册中知道 rdirsi 用于传递前两个参数

但是根据反汇编代码,rdi负责var a,也就是左边的param,意思应该是second参数

谁能帮我指出我的错误?

参数从左到右编号(归功于@R。发现这是您真正的困惑;我以为您在谈论 asm 指令的顺序,并且错过了问题的最后一段。)

我看起来很正常。 call swap指令运行时,

  • rdi 持有指向 a 的指针(堆栈上的一个本地指针),由
    设置 lea -0x4(%rbp),%raxmov %rax,%rdi.

    (而不只是 leardi,因为你没有启用优化。)

  • rsi 持有指向 shared 的指针,由 mov $shared,%esi
  • 设置
  • al 保持 0 因为您在调用函数之前没有定义函数或原型。 (即使没有 -Wall,gcc 也应该警告你)

.o 的反汇编显示 $shared 为 0,因为它尚未链接,所以它是符号的占位符(和 0 偏移量)。使用 objdump -drwC 查看重定位符号。 (我也喜欢 -Mintel,而不是 AT&T 语法。)

编译器的 asm 输出也更容易查看,您会在其中看到 $shared 而不是数字和符号引用。参见


写入寄存器的顺序无关紧要,重要的是它们在进入被调用函数时的值。

堆栈参数相同:如果编译器选择使用 mov 将参数写入堆栈,它可以按任何顺序执行。

只有当您选择使用 push 时,您才需要从右到左将第一个(最左边的)arg 留在最低地址,这是所有主流 C 调用约定所要求的未在寄存器中传递的参数(如果有)。

这种从右到左的顺序可能是 gcc -O0(没有优化,加上用于调试的反优化)选择按该顺序设置寄存器的原因,尽管这无关紧要。


顺便说一句,异或交换即使在没有 UB 的情况下正确实施也是无用且毫无意义的。 (Are there sequence points in the expression a^=b^=a^=b, or is it undefined?).

if(a==b) { *a = *b = 0; } else { int tmp = *a; *a=*b; *b=tmp; } 是一种更有效的交换,如果两个指针都指向同一个对象,它会保留安全的归零异或交换的行为。我猜你想要那个?为什么要使用异或交换?

如果不启用优化,编译器生成的 asm 基本上都会很烂,就像 main 的代码出于同样的原因很烂一样。如果这样做,swap 通常可以内联并成为零指令,或者只在寄存器之间花费最多 3 个 mov 指令;有时更少。 (编译器可以改变它的寄存器分配并决定 ab 现在在相反的寄存器中。)

你的想法 first/second 是错误的。从左边开始计数。

此外你的代码还有一些问题,至少:

  1. 您在调用 swap 时没有使用 declaration/prototype,因此虽然不必这样做,但 GCC 已选择生成如果它是可变参数函数就可以工作的代码(在 %rax 中存储 0)。

  2. swap的定义是一堆未定义的行为。