x86-64 上的 C++:何时在寄存器中传递和返回结构/类?

C++ on x86-64: when are structs/classes passed and returned in registers?(x86-64 上的 C++:何时在寄存器中传递和返回结构/类?)
本文介绍了x86-64 上的 C++:何时在寄存器中传递和返回结构/类?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着跟版网的小编来一起学习吧!

问题描述

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

Assuming the x86-64 ABI on Linux, under what conditions in C++ are structs passed to functions in registers vs. on the stack? Under what conditions are they returned in registers? And does the answer change for classes?

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

If it helps simplify the answer, you can assume a single argument/return value and no floating point values.

推荐答案

定义了 ABI 规范 此处.
此处提供了较新的版本.

The ABI specification is defined here.
A newer version is available here.

我假设读者已经习惯了文档的术语并且他们可以对原始类型进行分类.

I assume the reader is accustomed to the terminology of the document and that they can classify the primitive types.

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

If the object size is larger than two eight-bytes, it is passed in memory:

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,则在内存中传递:

If it is non POD, it is passed in memory:

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 is at work here

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

If it contains unaligned fields, it passed in memory:

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) 中的每一个进行分类.


If none of the above is true, the fields of the object are considered.
If one of the field is itself a struct/class the procedure is recursively applied.
The goal is to classify each of the two eight-bytes (8B) in the object.

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

The the class of the fields of each 8B are considered.
Note that an integral number of fields always totally occupy one 8B thanks to the alignment requirement of above.

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

Set C be the class of the 8B and D be the class of the field in consideration class.
Let new_class be pseudo-defined as

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,那么

Once we have the class of each 8Bs, say C1 and C2, than

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

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

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

Note This is my interpretation of the algorithm given in the ABI document.

示例

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 是整数,所以第一个 8B 是整数.b 是 X87 和 X87UP,所以第二个 8B 是 MEMORY.最后一节课是两个 8B 的 MEMORY.

a is INTEGER, so the first 8B is INTEGER. b is X87 and X87UP so the second 8B is MEMORY. The final class is MEMORY for both 8Bs.

示例

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 是整数.

a is SSE, so the first 8B is SSE.
b is INTEGER so the second 8B is INTEGER.

最终的类是计算出来的.

The final classes are the one calculated.

根据它们的类返回值:

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

  • MEMORY
    The caller passes an hidden, first, argument to the function for it to store the result into.
    In C++ this often involves a copy elision/return value optimisation. This address must be returned back into eax, thereby returning MEMORY classes "by reference" to an hidden, caller, allocated buffer.

如果类型有类 MEMORY,则调用者提供返回空间value 并在 %rdi 中传递这个存储的地址,就好像它是第一个函数的参数.实际上,这个地址首先变成了隐藏的"争论.返回时 %rax 将包含由%rdi 中的调用者.

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.

  • INTEGERPOINTER
    根据需要注册 raxrdx.

    SSESSEUP寄存器 xmm0xmm1 根据需要.

    SSE and SSEUP The registers xmm0 and xmm1 as needed.

    X87X87UP寄存器 st0

    技术定义是这里.

    ABI 的定义报告如下.

    The definition from the ABI is reported below.

    如果一个 de/constructor 是一个隐式声明的默认 de/constructor 并且如果:

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

        它的类没有虚函数和虚基类,并且
        其类的所有直接基类都有简单的析构函数,并且
        对于其类的所有非静态数据成员,属于类类型(或其数组),每个这样的类都有一个简单的 de/constructor.

        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 都是独立分类的,以便可以相应地通过.
    特别是,如果没有更多的参数寄存器,它们可能会在堆栈中结束.


    Note that each 8B is classified independently so that each one can be passed accordingly.
    Particularly, they may end up on the stack if there are no more parameter registers left.

    这篇关于x86-64 上的 C++:何时在寄存器中传递和返回结构/类?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持跟版网!

    本站部分内容来源互联网,如果有图片或者内容侵犯了您的权益,请联系我们,我们会在确认后第一时间进行删除!
  • 相关文档推荐

    Prevent class inheritance in C++(防止 C++ 中的类继承)
    Why should I declare a virtual destructor for an abstract class in C++?(为什么要在 C++ 中为抽象类声明虚拟析构函数?)
    Why is Default constructor called in virtual inheritance?(为什么在虚拟继承中调用默认构造函数?)
    C++ cast to derived class(C++ 转换为派生类)
    C++ virtual function return type(C++虚函数返回类型)
    Is there any real risk to deriving from the C++ STL containers?(从 C++ STL 容器派生是否有任何真正的风险?)