Bec*_*ter 5 c parameters assembly x86-64 calling-convention
我对 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;
}
Run Code Online (Sandbox Code Playgroud)
我使用以下命令编译两个文件:
gcc -c -fno-stack-protector a.c b.c
然后我objdump -d a.o检查ao的反汇编代码。
Disassembly of section .text:
0000000000000000 <main>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 48 83 ec 10 sub $0x10,%rsp
8: c7 45 fc 64 00 00 00 movl $0x64,-0x4(%rbp)
f: 48 8d 45 fc lea -0x4(%rbp),%rax
13: be 00 00 00 00 mov $0x0,%esi
18: 48 89 c7 mov %rax,%rdi
1b: b8 00 00 00 00 mov $0x0,%eax
20: e8 00 00 00 00 callq 25 <main+0x25>
25: b8 00 00 00 00 mov $0x0,%eax
2a: c9 leaveq
2b: c3 retq
Run Code Online (Sandbox Code Playgroud)
由于我的工作环境是Ubuntu 16.04 x86-64,我发现很难理解传递参数的顺序。
在我看来,默认调用约定在fastcall这里,因此参数是从右向左传递的。
我从 x86-64 System V ABI 手册中知道rdi和rsi用于传递前两个参数
但是,根据反汇编代码,rdi负责var a,这是左侧的参数,这意味着它应该是第二个参数。
有人可以帮我指出我的错误吗?
参数从左到右编号(归功于@R.,发现这是您真正的困惑;我以为您在谈论 asm 指令的顺序,并且错过了问题的最后一段。)
对我来说看起来很正常。当call swap指令运行时,
rdi保存一个指向(堆栈上的本地)的指针,由和a设置。
lea -0x4(%rbp),%raxmov %rax,%rdi
(而不是仅仅lea进入rdi,因为您没有启用优化。)
rsi保存一个指向 的指针shared,设置为mov $shared,%esial成立是0因为您在调用函数之前没有定义该函数或对其进行原型设计。(即使没有 ,gcc 也应该警告你这一点-Wall)反汇编显示.o为$shared0,因为它尚未链接,因此它是符号的占位符(和 0 偏移量)。用于objdump -drwC查看重定位符号。(我-Mintel也喜欢,而不是 AT&T 语法。)
同样更容易查看的是编译器的 asm 输出,您将在其中看到$shared而不是数字和符号引用。请参阅如何从 GCC/clang 程序集输出中消除“噪音”?。
写入寄存器的顺序并不重要,重要的是它们在进入被调用函数时的值。
堆栈参数也是如此:如果编译器选择将mov参数写入堆栈,它可以按任何顺序执行。
仅当您选择使用时,push您才必须从右向左将第一个(最左边的)参数保留在最低地址,这是所有主流 C 调用约定所要求的,对于未在寄存器中传递的参数(如果任何)。
这种从右到左的顺序可能就是为什么gcc -O0(没有优化,加上调试的反优化)选择按该顺序设置寄存器,即使它并不重要。
顺便说一句,即使在没有 UB 的情况下正确实现,xor-swap 也是无用且毫无意义的。(表达式 a^=b^=a^=b 中是否存在序列点,或者是否未定义?)。
if(a==b) { *a = *b = 0; } else { int tmp = *a; *a=*b; *b=tmp; }是一种更有效的交换,如果两个指针都指向同一个对象,则它保留归零的安全异或交换的行为。我想你想要那个?否则为什么要使用异或交换?
如果您不启用优化,编译器生成的 asm 基本上都会很糟糕,就像出于main同样原因的代码很糟糕一样。如果这样做,通常可以内联并且为零指令,或者在寄存器之间swap花费最多 3条指令;mov有时更少。(编译器可以更改其寄存器分配并决定a和b现在位于相反的寄存器中。)