问题描述
int func(int a,int b) { return a + b; }
和
void func(int a,int b,int * c) { *c = a + b; }
现在,如果是结构怎么办?
typedef struct { int a; int b; char c; } my;
my func(int a,char c) { my x; x.a = a; x.b = b; x.c = c; return x; }
和
void func(int a,int c,my * x) { x->a = a; x->b = b; x->c = c; }
我能想到的一件事是寄存器不能用于此目的,对吗?除此之外,我不知道通过编译器后该函数的结果。
哪个会更高效,更快捷?
解决方法
如果函数可以内联,则前2个之间通常没有区别。
否则(由于没有链接时间优化,所以没有内联)按值返回int
会更有效,因为它只是可以立即使用的寄存器中的值。同样,调用者不必传递尽可能多的参数,也不必查找/腾出空间来指向。如果调用者确实要使用输出值,则必须重新加载它,从而在从输入就绪到输出就绪的总依赖链中引入延迟。 (在现代x86 CPU上,存储转发延迟约为5个周期,而为{x1-} System x86-64 System V实现该功能的lea eax,[rdi + rsi]
的延迟为1个周期。
例外情况可能是在极少数情况下,即调用方不打算使用该值,而只是希望将其存储在某个地址的内存中。将该地址传递给被调用方(在寄存器中)以便可以在该地址使用,这意味着调用方不必将该地址保留在可以在整个函数调用中保留的任何位置。
对于结构版本:
不能将寄存器用于此目的,对吗?
不,对于某些调用约定,可以在寄存器中返回小的结构。
x86-64系统V将在RDX:RAX寄存器对中按值返回my
结构,因为它小于16个字节并且都是整数。 (并且可以轻松复制。)在https://godbolt.org/z/x73cEh-
# clang11.0 -O3 for x86-64 SysV
func_val:
shl rsi,32
mov eax,edi
or rax,rsi # (uint64_t)b<<32 | a; the low 64 bits of the struct
# c was already in EDX,the low half of RDX; clang leaves it there.
ret
func_out:
mov dword ptr [rcx],edi
mov dword ptr [rcx + 4],esi # just store the struct members
mov byte ptr [rcx + 8],dl # to memory pointed-to by 4th arg
ret
GCC并不假设char c
像clang那样正确地扩展到EDX(unofficial ABI feature)。 GCC进行了真正的哑字节存储/双字重载,从而创建了存储转发停顿,以便从内存而不是从EDX的高字节中获取未初始化的垃圾。纯粹是错过的优化,但请参见https://godbolt.org/z/WGcqKc。在执行movq rax,xmm0
之前,它还疯狂地使用SSE2将这两个整数合并为一个64位值,或者将其合并为输出参数的内存。
如果调用者使用这些值,您肯定希望struct版本内联,这样可以优化打包到返回值寄存器中。
How does function ACTUALLY return struct variable in C?有一个用于较大结构的ARM示例:按值返回将隐藏的指针传递给调用者的返回值对象。从那里,如果分配给逃避分析无法证明是私有的内容,调用者可能需要复制它。 (例如,通过某些指针)。 What prevents the usage of a function argument as hidden pointer?
也相关:Why is tailcall optimization not performed for types of class MEMORY?
How do C compilers implement functions that return large structures?指出C和C ++之间的代码生成可能有所不同。
我不知道如何解释在不了解asm和您关心的调用约定的情况下可以应用的任何一般经验法则。通常通过引用传递/返回 large 结构,但是对于小型结构,这很大程度上取决于它。