常见的有 eax 传递,也有和 edx 联合返回

如果要返回一个大家伙呢?

  1. 首先调用方在栈上开辟一段空间,将这个空间的一部分作为传递返回值的临时对象,这里称为 tmp
  2. 将 tmp 对象的地址作为隐藏参数传递给函数
  3. 函数将数据拷贝给 tmp 对象,并将对象的地址用 eax 传出
  4. 返回之后,调用方将 eax 指向的 tmp 对象的内容拷贝给真正要赋值的对象 (可以优化成直接在函数内赋值了)
#include <string.h>
 
typedef struct big_thing {
    char buf[128];
} big_thing;
 
big_thing return_test() {
    big_thing b;
    memcpy(b.buf, "Hello, World!", 13);
    return b;
}
 
int main() {
    big_thing b = return_test();
    return 0;
}
 
_QWORD *__fastcall return_test(_QWORD *a1)
{
  __int64 v1; // rbx
  __int64 v2; // rbx
  __int64 v3; // rbx
  __int64 v4; // rbx
  __int64 v5; // rbx
  __int64 v6; // rbx
  __int64 v7; // rbx
  __int64 v8; // rbx
  char dest[128]; // [rsp+10h] [rbp-90h] BYREF
 
  memcpy(dest, "Hello, World!", 0xDuLL);
  v1 = *(_QWORD *)&dest[8];
  *a1 = *(_QWORD *)dest;
  a1[1] = v1;
  v2 = *(_QWORD *)&dest[24];
  a1[2] = *(_QWORD *)&dest[16];
  a1[3] = v2;
  v3 = *(_QWORD *)&dest[40];
  a1[4] = *(_QWORD *)&dest[32];
  a1[5] = v3;
  v4 = *(_QWORD *)&dest[56];
  a1[6] = *(_QWORD *)&dest[48];
  a1[7] = v4;
  v5 = *(_QWORD *)&dest[72];
  a1[8] = *(_QWORD *)&dest[64];
  a1[9] = v5;
  v6 = *(_QWORD *)&dest[88];
  a1[10] = *(_QWORD *)&dest[80];
  a1[11] = v6;
  v7 = *(_QWORD *)&dest[104];
  a1[12] = *(_QWORD *)&dest[96];
  a1[13] = v7;
  v8 = *(_QWORD *)&dest[120];
  a1[14] = *(_QWORD *)&dest[112];
  a1[15] = v8;
  return a1;
}
 
int __cdecl main(int argc, const char **argv, const char **envp)
{
  char v4[128]; // [rsp+0h] [rbp-80h] BYREF
 
  return_test(v4);
  return 0;
}

细究的话就要涉及到 move 了