C++ 逐个传递参数,或将参数包装到数组、结构或元组中
当向函数传递参数时,我总是假设逐个传递参数与传递封装在数组、结构或元组中的参数没有什么不同。然而,一个简单的实验表明我错了 以下程序将在以下情况下运行:C++ 逐个传递参数,或将参数包装到数组、结构或元组中,c++,compiler-optimization,calling-convention,C++,Compiler Optimization,Calling Convention,当向函数传递参数时,我总是假设逐个传递参数与传递封装在数组、结构或元组中的参数没有什么不同。然而,一个简单的实验表明我错了 以下程序将在以下情况下运行: int测试(int a、int b、int c、int d){ 返回a+b+c+d; } int测试(标准::数组arr){ 返回arr[0]+arr[1]+arr[2]+arr[3]; } 结构abcd{ int a;int b;int c;int d; }; 整数测试(s){ 返回s.a+s.b+s.c+s.d; } int测试(std::
int测试(int a、int b、int c、int d){
返回a+b+c+d;
}
int测试(标准::数组arr){
返回arr[0]+arr[1]+arr[2]+arr[3];
}
结构abcd{
int a;int b;int c;int d;
};
整数测试(s){
返回s.a+s.b+s.c+s.d;
}
int测试(std::tuple-tup){
返回std::get(tup)+std::get(tup)+std::get(tup)+std::get(tup);
}
…产生各种装配输出:
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
impl_测试(int,int,int,int):
lea eax,[rdi+rsi]
添加eax、edx
添加eax、ecx
ret
impl_测试(标准::阵列):
莫夫拉克斯,rdi
sar rax,32岁
添加eax、edi
添加eax、esi
sar rsi,32
添加eax、esi
ret
实施测试(abcd):
莫夫拉克斯,rdi
sar rax,32岁
添加eax、edi
添加eax、esi
sar rsi,32
添加eax、esi
ret
impl_测试(std::tuple):
mov eax,DWORD PTR[rdi+8]
添加eax、DWORD PTR[rdi+12]
添加eax、DWORD PTR[rdi+4]
添加eax、DWORD PTR[rdi]
ret
主要内容:
推动rbp
推送rbx
mov ecx,4
mov edx,3
movabs rbp,8589934592
电影esi,2
副区长,24
mov edi,1
movabs rbx,17179869184
调用int测试(int,int,int,int)
移动rdi,rbp
移动rsi,rbx
或rbx,3
或rdi,1
或rsi,3
调用int测试(std::array)
移动rdi,rbp
移动rsi,rbx
或rdi,1
调用整数测试(abcd)
移动rdi,rsp
mov DWORD PTR[rsp],4
莫夫·德沃德PTR[rsp+4],3
莫夫·德沃德PTR[rsp+8],2
莫夫·德沃德PTR[rsp+12],1
调用int测试(std::tuple)
加上rsp,24
异或eax,eax
流行音乐
流行限制性商业惯例
ret
为什么会有差异?当调用函数时(即,不是内联的,constepr
计算或消除),参数的传递方式取决于许多因素,包括:
- 如果参数是基元类型,则该参数是整数还是浮点
- 参数的类型
- 其地址是否在被调用方的某些非消除代码中获取
- 默认或指定的调用约定
- 是否正在使用全程序优化(WPO)
- 被调用方是否位于共享库、静态库或对象文件中,或位于同一翻译单元中
- 指定的浮点行为
- 目标平台
- 参数在参数列表中的位置
-02
编译代码,因此死代码不会被消除,函数内联被禁用。因此,必须调用所有函数。目标平台是x64
第一个函数有四个4字节整数参数。因此,它们都是通过寄存器传递的
第二个函数有一个由四个4字节整数组成的固定大小数组。编译器决定使用两个寄存器(rdi
和rsi
)传递四个整数,其中rdi
=0x20000001和rsi
=0x400000003。注意四个整数(1,2,3,4)是如何使用这两个寄存器紧凑地传递的
将整数作为结构传递而不是逐个传递使编译器使用不同的技术来传递它们。但是,在代码大小、速度和所需寄存器数量之间存在一种权衡
第三个函数也是如此
但是,最后一个函数包含对
std::get
的调用,这些调用需要传递的元组的地址。因此,地址存储在rdi
中,供std::get
函数使用。因为您是用C++14编译的,所以std::get用constexpr
标记。编译器能够评估函数,因此在测试函数中发出内存访问,而不是发出对std::get
函数的调用。请注意,这与内联不同。只要最终结果相同,编译器通常不能做任何他们想做的事情?@AndyG Yes和no。还有基于语言和平台的调用约定,规定如何调用。如果没有这些东西(比如从右到左在寄存器中传递参数),就不可能将来自不同源语言的各种对象文件链接在一起并生成可靠的代码。结构的传递就好像各个成员是分开的一样。但是优化器(或缺少)在将64位下转换为32位时做了一些奇怪的事情。@imreal编译对象文件时,参数的位置需要严格遵循调用约定。所以编译器不能“随心所欲”,除非它内联。但是在这种情况下,OP并没有测试内联情况。这取决于ABI。表示参数在单独的寄存器中传递,因此具有四个int
参数的第一个参数使用四个寄存器。std::array和abcd
非常容易复制,并且足够小,可以通过寄存器传递,但是它们被打包到两个寄存器中。元组似乎是在内存中传递的,但我不太清楚为什么。
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