x86-64 : when are structs/classes passed and returned in registers? 上的 C++

标签 c++ assembly x86-64 calling-convention abi

假设 Linux 上的 x86-64 ABI,在 C++ 中的什么条件下,结构传递给函数是在寄存器中还是在堆栈中?在什么情况下它们会返回到寄存器中?类(class)的答案会改变吗?

如果它有助于简化答案,您可以假设一个参数/返回值并且没有浮点值。

最佳答案

ABI 规范定义here .
有更新版本可用 here .

我假设读者已经习惯了文档的术语,并且他们可以对基本类型进行分类。


如果对象大小大于两个八字节,则在内存中传递:

struct foo
{
    unsigned long long a;
    unsigned long long b;
    unsigned long long c;               //Commenting this gives mov rax, rdi
};

unsigned long long foo(struct foo f)
{ 
  return f.a;                           //mov     rax, QWORD PTR [rsp+8]
} 

如果是非POD,则在内存中传递:

struct foo
{
    unsigned long long a;
    foo(const struct foo& rhs){}            //Commenting this gives mov rax, rdi
};

unsigned long long foo(struct foo f)
{
  return f.a;                               //mov     rax, QWORD PTR [rdi]
}

Copy elision在这里工作

如果它包含未对齐的字段,则在内存中传递:

struct __attribute__((packed)) foo         //Removing packed gives mov rax, rsi
{
    char b;
    unsigned long long a;
};

unsigned long long foo(struct foo f)
{
  return f.a;                             //mov     rax, QWORD PTR [rsp+9]
}

如果以上都不成立,则考虑对象的字段。
如果其中一个字段本身是结构/类,则递归应用该过程。
目标是对对象中的两个八字节 (8B) 中的每一个进行分类。

考虑了每个8B字段的类。
请注意,由于上述对齐要求,整数个字段总是完全占用一个 8B。

设置C为8B的类,D为考虑类中字段的类。
new_class被伪定义为

cls new_class(cls D, cls C)
{
   if (D == NO_CLASS)
      return C;

   if (D == MEMORY || C == MEMORY)
      return MEMORY;

   if (D == INTEGER || C == INTEGER)
      return INTEGER;

   if (D == X87 || C == X87 || D == X87UP || C == X87UP)
      return MEMORY;

   return SSE;
}

然后8B的类别计算如下

C = NO_CLASS;

for (field f : fields)
{
    D = get_field_class(f);        //Note this may recursively call this proc
    C = new_class(D, C);
}

一旦我们有了每个 8B 的类,比如 C1 和 C2,那么

if (C1 == MEMORY || C2 == MEMORY)
    C1 = C2 = MEMORY;

if (C2 == SSEUP AND C1 != SSE)
   C2 = SSE;

注意这是我对ABI文档中给出的算法的解释。


示例

struct foo
{
    unsigned long long a;
    long double b;
};

unsigned long long foo(struct foo f)
{
  return f.a;
}

8B 及其领域

前 8B:a 第二个 8B:b

a是INTEGER,所以第一个8B是INTEGER。 b 是 X87 和 X87UP 所以第二个 8B 是 MEMORY。 两个 8B 的最后一类是 MEMORY。


示例

struct foo
{
    double a;
    long long b;
};

long long foo(struct foo f)
{
  return f.b;                     //mov rax, rdi
}

8B 及其领域

前 8B:a 第二个 8B:b

a是SSE,所以前8B是SSE。
b 是整数,所以第二个 8B 是整数。

最终的类是计算出来的类。


返回值

值相应地返回给它们的类:

  • 内存
    调用者将一个隐藏的第一个参数传递给函数,以便将结果存储到其中。
    在 C++ 中,这通常涉及复制省略/返回值优化。 此地址必须返回到 eax 中,从而将 MEMORY 类“通过引用”返回到隐藏的调用者分配的缓冲区。

    If the type has class MEMORY, then the caller provides space for the return value and passes the address of this storage in %rdi as if it were the first argument to the function. In effect, this address becomes a “hidden” first argument. On return %rax will contain the address that has been passed in by the caller in %rdi.

  • 整数指针
    根据需要注册 raxrdx

  • SSESSEUP 根据需要注册 xmm0xmm1

  • X87X87UP 寄存器 st0


POD

技术定义是here .

ABI 的定义报告如下。

A de/constructor is trivial if it is an implicitly-declared default de/constructor and if:

   • its class has no virtual functions and no virtual base classes, and
   • all the direct base classes of its class have trivial de/constructors, and
   • for all the nonstatic data members of its class that are of class type (or array thereof), each such class has a trivial de/constructor.


注意每个8B都是独立分类的,这样每个8B都可以相应的通过。
特别是,如果没有更多的参数寄存器,它们可能最终会进入堆栈。

关于x86-64 : when are structs/classes passed and returned in registers? 上的 C++,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42411819/

相关文章:

c++ - 如何计算 Lucas Kanade 流

c++ - 指针的模板数组类

assembly - ESP 在/proc/pid/maps 和二进制文件中不同

assembly - 简单的 NASM "boot program"没有正确访问内存?

linux - 等待按键 组装 NASM,Linux

c++ - jGRASP c++ 安装问题找不到-lfeglut

c++ - 使用 Boost [C++] 将 http 文件内容读取为字符串

c - 将 arm 的汇编代码插入到 C 函数中,如何插入外部变量?

windows - 为什么 OSX 在 amd64 间接跳转时出现总线错误?

macos - ld 链接器错误 - 体系结构 x86_64 的 undefined symbol