x86-64上的C ++:何时在寄存器中传递和返回结构/类? [英] C++ on x86-64: when are structs/classes passed and returned in registers?

查看:154
本文介绍了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]
}


如果以上所有条件都不成立,那么将考虑对象的字段.
如果字段之一本身是struct/class,则将递归应用该过程.
目标是对对象中的两个八个字节(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的等级

then the class of the 8B is computed as follow

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是INTEGER,所以第一个8B是INTEGER. 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是INTEGER,所以第二个8B是INTEGER.

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.

这些值将相应地返回到其类:

The values are returned accordingly to their classes:

  • 记忆
    调用方将一个隐藏的第一个参数传递给该函数,以将结果存储到该函数中.
    在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类,则调用方为返回提供空间 值,并以%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.

  • INTEGER POINTER
    寄存器raxrdx根据需要.

  • INTEGER and POINTER
    The registers rax and rdx as needed.

    SSE SSEUP 寄存器xmm0xmm1根据需要.

    SSE and SSEUP The registers xmm0 and xmm1 as needed.

    X87 X87UP 寄存器st0

    技术定义是此处.

    以下是ABI的定义.

    如果de/constructor是隐式声明的默认de/constructor,并且满足以下条件,则它是微不足道的:

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

      •其类没有虚函数,也没有虚拟基类,并且
      •该类中的所有直接基类都具有琐碎的de/constructor,并且
    对于该类的所有非静态数据成员,这些非静态数据成员都是类类型(或其数组),每个此类都具有琐碎的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都是独立分类的,因此每个相应的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 ++:何时在寄存器中传递和返回结构/类?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

  • 查看全文
    相关文章
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆