我对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 $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
由于我的工作环境是 Ubuntu 16.04 x86-64 ,所以我很难理解传递参数的顺序。
在我看来,默认的调用约定是fastcall
,因此参数是从从右向左传递的。
根据x86-64 System V ABI手册,我知道rdi
和rsi
用于传递前两个参数
但是,根据反汇编代码,rdi
是var a
的责任,它是左侧的参数,意味着它应该是 second 参数。>
有人可以帮我指出我的错误吗?
答案 0 :(得分:3)
您的第一/第二个想法是错误的。从左侧开始计数。
此外,您的代码至少有一些问题:
您正在调用没有声明/原型的swap
,因此虽然不是必须的,但GCC选择生成的代码在可变参数函数中有效(在{{中存储0 1}})。
%rax
的定义是未定义行为的堆。
答案 1 :(得分:2)
Args的编号从左到右(贷记为@R。因为发现这是您的实际困惑;我认为您是在谈论asm指令的顺序,因此错过了最后一段问题。)
对我来说很正常。当m2 = random.randint(0,9)
m1 = random.randint(0,9)
b = random.randint(0,9)
funk = lambda i : m2*i**2 + m1*i + b
指令运行时,
call swap
持有指向rdi
(堆栈上的本地变量)的指针,该指针由
设置
a
和lea -0x4(%rbp),%rax
。
(而不是mov %rax,%rdi
进入lea
,因为您没有启用优化。)
rdi
持有指向rsi
设置的shared
的指针mov $shared,%esi
之所以拥有al
,是因为您在调用函数之前没有定义函数或对其进行原型设计。 (即使没有0
,gcc也会警告您) -Wall
的反汇编显示.o
为0,因为尚未链接,因此它是符号的占位符(偏移量为0)。使用$shared
查看重定位符号。 (我也喜欢objdump -drwC
,而不是AT&T语法。)
更容易看的是编译器的asm输出,您将在其中看到-Mintel
而不是数字和符号引用。参见How to remove "noise" from GCC/clang assembly output?。
写入什么顺序寄存器都没有关系,仅在进入被调用函数时才赋值。
与堆栈args相同:如果编译器选择使用$shared
将args写入堆栈,则它可以按任何顺序进行操作。
仅当您选择使用mov
时,才需要从右到左将第一个(最左边的)arg保留在最低地址,这是所有主流args的C调用约定所要求的。没有传入寄存器(如果有)。
从右到左的顺序可能是push
(无优化,加上调试的反优化)选择以该顺序设置寄存器的原因,即使这无关紧要。
而且顺便说一句,即使没有UB正确实施,xor-swap也是毫无意义的。 (Are there sequence points in the expression a^=b^=a^=b, or is it undefined?)。
gcc -O0
是一种更有效的交换,如果两个指针都指向同一个对象,则该交换将保留安全的零异或交换行为。我想你想要那个吗?为什么还要使用异或交换?
如果您不启用优化功能,则任一编译器生成的asm基本上都会烂掉,就像if(a==b) { *a = *b = 0; } else { int tmp = *a; *a=*b; *b=tmp; }
的代码出于同样的原因烂掉一样。而且,如果这样做,main
通常可以内联并且为零指令,或者在寄存器之间最多花费3条swap
指令;有时更少。 (编译器只需更改其寄存器分配,然后确定mov
和a
现在位于相反的寄存器中。)