use*_*145 2 assembly x86-64 calling-convention
我在理解这个汇编代码的作用时遇到了问题(这是一个较大的汇编代码,这是Intel语法):
vector<int> func(int i) { ...} // C++ source
Run Code Online (Sandbox Code Playgroud)
来自Godbolt编译器资源管理器的 clang输出:
func(int): # @func(int)
push rbp
push rbx
push rax
mov ebp, esi
mov rbx, rdi
xorps xmm0, xmm0
movups xmmword ptr [rbx], xmm0
mov qword ptr [rbx + 16], 0
Run Code Online (Sandbox Code Playgroud)
这是在Linux上编译的,遵循官方的System V AMD64 ABI.根据此链接,rdi寄存器用于将第一个参数传递给函数.所以在这条线上
mov rbx, rdi
Run Code Online (Sandbox Code Playgroud)
我们将参数的值(在本例中为int)移动到rbx.不久之后,我们做到:
movups xmmword ptr [rbx], xmm0
Run Code Online (Sandbox Code Playgroud)
这是我不明白的.rbx包含参数的值,它是一个int,这里我们将xmm0的内容复制到rbx指向的地址(但是rbx不包含任何地址,只是函数的参数!)
有些事我错了,但我无法弄明白为什么.
在Linux和Windows以外的大多数其他64位x86操作系统使用的SysV 64位ABI中,struct或者class返回值rax或rdx寄存器中的返回值,或者通过作为第一个参数传递的隐藏指针返回.
两个选项之间的决定主要取决于返回结构的大小:大于16字节的结构通常使用隐藏指针方法,但也有其他因素,我建议这个答案进行更全面的处理.
当使用隐藏指针方法时,我们需要一种方法将此指针传递给函数.在这种情况下,指针的行为就像它是第一个参数(传入rdi)一样,它将其他参数转移到后面的位置2.
我们可以通过检查为返回struct1到5个int值的对象(因此在该平台上4到20个字节)生成的代码来清楚地看到这一点.C++代码:
struct one {
int x;
};
struct two {
int x1, x2;
};
struct three {
int x1, x2, x3;
};
struct four {
int x1, x2, x3, x4;
};
struct five {
int x1, x2, x3, x4, x5;
};
one makeOne() {
return {42};
}
two makeTwo() {
return {42, 52};
}
three makeThree() {
return {42, 52, 62};
}
four makeFour() {
return {42, 52, 62, 72};
}
five makeFive() {
return {42, 52, 62, 72, 82};
}
Run Code Online (Sandbox Code Playgroud)
结果在6.0 中的以下程序集中clang(但其他编译器的行为类似:
makeOne(): # @makeOne()
mov eax, 42
ret
makeTwo(): # @makeTwo()
movabs rax, 223338299434
ret
makeThree(): # @makeThree()
movabs rax, 223338299434
mov edx, 62
ret
makeFour(): # @makeFour()
movabs rax, 223338299434
movabs rdx, 309237645374
ret
.LCPI4_0:
.long 42 # 0x2a
.long 52 # 0x34
.long 62 # 0x3e
.long 72 # 0x48
makeFive(): # @makeFive()
movaps xmm0, xmmword ptr [rip + .LCPI4_0] # xmm0 = [42,52,62,72]
movups xmmword ptr [rdi], xmm0
mov dword ptr [rdi + 16], 82
mov rax, rdi
ret
Run Code Online (Sandbox Code Playgroud)
基本模式是至多且包括8个字节,则struct在完全返回rax(包括包装在64位寄存器的多个更小的值),以及用于对象最多16个字节都rax与rdx使用1.
之后,策略完全改变,我们看到内存写入发生在指向的位置rdi- 这是上面提到的隐藏指针方法.
最后,来包装它全部,我们注意到,sizeof(vector<int>)是通常的24个字节的64位平台,而且是绝对的大C++在Linux上的编译器24个字节-这样的隐藏指针的方法适用于矢量.
感谢Jester已经在评论中以更简洁的形式回答了这个问题.
1像223338299434这样的奇怪常量被存储到64位寄存器只是一个优化:编译器只是将两个32位存储组合成一个64位常量,就像在52ul << 32 | 42ul其中产生的那样223338299434.
2这与用于传递this成员函数的方法相同:在成员函数也返回使用隐藏指针方法传递的值的情况下,隐藏指针首先(in rdi),然后是this指针(in rsi)然后最后是第一个用户提供的参数(通常在rdx- 但这取决于类型).这是一个例子.