在ASM中,c编译器如何处理函数的结构返回值

在谈到C函数的返回值时,返回值存储在EAX寄存器中。 假设我们正在谈论32位寄存器,欢迎整数,但是当我们返回这些类型时会发生什么: long longlong double ,一个大于32位的struct / union

在常见的x86调用约定中,在RDX:RAX中返回适合两个寄存器的对象。 这是与div和mul指令的隐式输入/输出相同的寄存器对,以及cdq / cqo (符号扩展e / rax到e / rdx)。

i386 Linux(SysV)调用约定只返回64位整数。 结构(甚至是由单个int32_t组成的结构)使用隐藏参数方法而不是打包的eaxedx:eax 。 64位Linux和微软目前的标准__vectorcall都将结构打包成e/raxe/rdx:e/rax

许多调用约定通过添加一个隐藏的额外参数来处理更大的对象:一个指向空间的指针,用于存储返回值。 有关您正在使用的特定ABI,请参阅ABI文档。 (链接在x86维基)。

与注释中讨论的其他调用约定相比(例如,隐式使用堆栈上的空间来存储返回的大对象),传递指针可以保存副本,因为指针可以指向最终目标而不是堆栈上的临时空间。

考虑这个程序:

 struct object_t { int m1; int m2; int m3; }; struct object_t test1(void) { struct object_t o = {1, 2, 3}; return o; } long long test2(void) { return 0LL; } long double test3(void) { return 0.0L; } 

在Windows上编译(目标文件,最小指令,没有x87指令):

 $ gcc -Wall -c -O2 -mno-80387 test.c -o test.o 

第一个function:

 00000000 <_test1>: 0: 8b 44 24 04 mov eax,DWORD PTR [esp+0x4] 4: c7 00 01 00 00 00 mov DWORD PTR [eax],0x1 a: c7 40 04 02 00 00 00 mov DWORD PTR [eax+0x4],0x2 11: c7 40 08 03 00 00 00 mov DWORD PTR [eax+0x8],0x3 18: c3 ret 

调用者将提供指向其结构在堆栈上的位置的指针作为第一个参数, test1将使用该指针填充它。

第二个函数( sizeof(long long) == 8 ):

 00000020 <_test2>: 20: 31 c0 xor eax,eax 22: 31 d2 xor edx,edx 24: c3 ret 

结果将通过两个寄存器eaxedx返回,而不仅仅是eax

第三个函数( sizeof(long double) == 12 ):

 00000030 <_test3>: 30: 31 c0 xor eax,eax 32: 31 d2 xor edx,edx 34: 31 c9 xor ecx,ecx 36: c3 ret 

返回值将通过三个寄存器eaxedxecx传递。