Sta*_*ked 15 c++ calling-convention compiler-optimization
将参数传递给函数时,我总是假设逐个传递参数与传递它们包装在数组或结构或元组中没有什么不同.然而,一个简单的实验表明我错了.
使用GCC编译时的以下程序:
int test(int a, int b, int c, int d) {
return a + b + c + d;
}
int test(std::array<int, 4> arr) {
return arr[0] + arr[1] + arr[2] + arr[3];
}
struct abcd {
int a; int b; int c; int d;
};
int test(abcd s) {
return s.a + s.b + s.c + s.d;
}
int test(std::tuple<int, int, int, int> tup) {
return std::get<0>(tup) + std::get<1>(tup) + std::get<2>(tup) + std::get<3>(tup);
}
Run Code Online (Sandbox Code Playgroud)
...产生各种装配输出:
impl_test(int, int, int, int):
lea eax, [rdi+rsi]
add eax, edx
add eax, ecx
ret
impl_test(std::array<int, 4ul>):
mov rax, rdi
sar rax, 32
add eax, edi
add eax, esi
sar rsi, 32
add eax, esi
ret
impl_test(abcd):
mov rax, rdi
sar rax, 32
add eax, edi
add eax, esi
sar rsi, 32
add eax, esi
ret
impl_test(std::tuple<int, int, int, int>):
mov eax, DWORD PTR [rdi+8]
add eax, DWORD PTR [rdi+12]
add eax, DWORD PTR [rdi+4]
add eax, DWORD PTR [rdi]
ret
main:
push rbp
push rbx
mov ecx, 4
mov edx, 3
movabs rbp, 8589934592
mov esi, 2
sub rsp, 24
mov edi, 1
movabs rbx, 17179869184
call int test<int, int, int, int>(int, int, int, int)
mov rdi, rbp
mov rsi, rbx
or rbx, 3
or rdi, 1
or rsi, 3
call int test<std::array<int, 4ul> >(std::array<int, 4ul>)
mov rdi, rbp
mov rsi, rbx
or rdi, 1
call int test<abcd>(abcd)
mov rdi, rsp
mov DWORD PTR [rsp], 4
mov DWORD PTR [rsp+4], 3
mov DWORD PTR [rsp+8], 2
mov DWORD PTR [rsp+12], 1
call int test<std::tuple<int, int, int, int> >(std::tuple<int, int, int, int>)
add rsp, 24
xor eax, eax
pop rbx
pop rbp
ret
Run Code Online (Sandbox Code Playgroud)
为什么会有区别?
当调用函数时(即未内联、constexpr求值或消除),传递参数的方式取决于许多因素,包括:
让我们回到您提供的示例。您编译了代码,-02因此死代码不会被消除,并且函数内联被禁用。所以所有的函数都必须被调用。目标平台是x64。
第一个函数有四个 4 字节整数参数。因此,它们全部通过寄存器传递。
第二个函数有一个由四个 4 字节整数组成的固定大小数组。编译器决定使用两个寄存器(rdi和rsi)来传递四个整数 where rdi= 0x200000001 和rsi= 0x400000003。请注意如何使用这两个寄存器紧凑地传递四个整数(1、2、3、4)。
将整数作为结构而不是逐个传递使得编译器使用不同的技术来传递它们。但这里需要在代码大小、速度和所需寄存器数量之间进行权衡。
第三个函数也是如此。
然而,最后一个函数包含std::get需要传递元组地址的调用。因此该地址被存储rdi以供std::get函数使用。由于您使用 C++14 进行编译,因此 std::get 标记为constexpr。编译器能够评估该函数,因此内存访问已在测试函数中发出,而不是发出对该std::get函数的调用。请注意,这与内联不同。