在此编译器输出中,为什么 func(int) 使用其第一个 arg 作为指针,将指向内存的 24 个字节归零? arg 不是指针
In this compiler output, why does func(int) use its first arg as a pointer, zeroing 24 bytes of pointed-to memory? The arg isn't a pointer
我在理解这段汇编代码的作用时遇到了问题(这是更大的汇编代码的一小部分,这是英特尔语法):
vector<int> func(int i) { ...} // C++ source
clang 输出 from the Godbolt compiler explorer:
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
这是在 Linux 上编译的,遵循官方的 System V AMD64 ABI。
根据 this link,rdi 寄存器用于将第一个参数传递给函数。
所以在这一行
mov rbx, rdi
我们将参数的值(在本例中为 int)移动到 rbx。不久之后,我们做:
movups xmmword ptr [rbx], xmm0
这就是我不明白的地方。 rbx 包含参数的值,它是一个int,这里我们将xmm0 的内容复制到rbx 指向的地址(但是rbx 不包含任何地址,只是函数的参数!)
有些地方我弄错了,但我不知道为什么。
在 Linux 和 Windows 之外的大多数其他 64 位 x86 操作系统使用的 SysV 64 位 ABI 中,struct
或 class
return 值在 rax
或 rdx
寄存器中被 return 编辑,或者通过作为第一个参数传递的 隐藏指针 。
两个选项之间的决定主要取决于 returned 结构的大小:大于 16 字节的结构通常使用隐藏指针方法,但还有其他因素,我建议 以获得更全面的治疗。
当使用隐藏指针方法时,我们需要一种方法将此指针传递给函数。在这种情况下,指针的行为就好像它是第一个参数(传入 rdi
),它将其他参数移到后面的位置 2。
我们可以通过检查为函数生成的代码清楚地看到这一点 returning struct
对象的 1 到 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};
}
following assembly 在 clang
6.0 中的结果(但其他编译器的行为类似:
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
基本模式是最多并包括 8 个字节,struct
完全 returned in rax
(包括在 64 位寄存器中打包多个较小的值),并且对于最多 16 字节的对象,使用 rax
和 rdx
1.
之后策略完全变了,我们看到rdi
指向的位置发生了内存写入——这就是上面提到的隐藏指针的做法。
最后,总结一下,我们注意到 sizeof(vector<int>)
在 Linux 上的主要 C++ 编译器上是 on 64-bit platforms, and is definitely 24 字节 - 因此隐藏指针方法适用于向量。
感谢 Jester,他已经以更简短的形式回答了这个问题,。
1 存储到 64 位寄存器中的奇怪常量,如 223338299434
只是一种优化:编译器只是组合了两个 32 位存储转换为单个 64 位常量,如 52ul << 32 | 42ul
中的结果 223338299434
.
2 这与用于为成员函数传递 this
的方法相同:在成员函数 also returns 一个通过隐藏指针方法传递的值,首先是隐藏指针(在 rdi
中),然后是 this
指针(在 rsi
中),最后是第一个用户提供的参数(通常在 rdx
中——但这取决于类型)。这里是 an example.
我在理解这段汇编代码的作用时遇到了问题(这是更大的汇编代码的一小部分,这是英特尔语法):
vector<int> func(int i) { ...} // C++ source
clang 输出 from the Godbolt compiler explorer:
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
这是在 Linux 上编译的,遵循官方的 System V AMD64 ABI。 根据 this link,rdi 寄存器用于将第一个参数传递给函数。 所以在这一行
mov rbx, rdi
我们将参数的值(在本例中为 int)移动到 rbx。不久之后,我们做:
movups xmmword ptr [rbx], xmm0
这就是我不明白的地方。 rbx 包含参数的值,它是一个int,这里我们将xmm0 的内容复制到rbx 指向的地址(但是rbx 不包含任何地址,只是函数的参数!)
有些地方我弄错了,但我不知道为什么。
在 Linux 和 Windows 之外的大多数其他 64 位 x86 操作系统使用的 SysV 64 位 ABI 中,struct
或 class
return 值在 rax
或 rdx
寄存器中被 return 编辑,或者通过作为第一个参数传递的 隐藏指针 。
两个选项之间的决定主要取决于 returned 结构的大小:大于 16 字节的结构通常使用隐藏指针方法,但还有其他因素,我建议
当使用隐藏指针方法时,我们需要一种方法将此指针传递给函数。在这种情况下,指针的行为就好像它是第一个参数(传入 rdi
),它将其他参数移到后面的位置 2。
我们可以通过检查为函数生成的代码清楚地看到这一点 returning struct
对象的 1 到 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};
}
following assembly 在 clang
6.0 中的结果(但其他编译器的行为类似:
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
基本模式是最多并包括 8 个字节,struct
完全 returned in rax
(包括在 64 位寄存器中打包多个较小的值),并且对于最多 16 字节的对象,使用 rax
和 rdx
1.
之后策略完全变了,我们看到rdi
指向的位置发生了内存写入——这就是上面提到的隐藏指针的做法。
最后,总结一下,我们注意到 sizeof(vector<int>)
在 Linux 上的主要 C++ 编译器上是
感谢 Jester,他已经以更简短的形式回答了这个问题,
1 存储到 64 位寄存器中的奇怪常量,如 223338299434
只是一种优化:编译器只是组合了两个 32 位存储转换为单个 64 位常量,如 52ul << 32 | 42ul
中的结果 223338299434
.
2 这与用于为成员函数传递 this
的方法相同:在成员函数 also returns 一个通过隐藏指针方法传递的值,首先是隐藏指针(在 rdi
中),然后是 this
指针(在 rsi
中),最后是第一个用户提供的参数(通常在 rdx
中——但这取决于类型)。这里是 an example.