当前位置: 首页 > 知识库问答 >
问题:

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

孟胤
2023-03-14

假设Linux上的x86-64 ABI,在C语言中,在什么条件下结构被传递给寄存器中的函数,而不是堆栈中的函数?在什么条件下,它们会在登记簿中返回?答案会随着班级而改变吗?

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

共有2个答案

艾正浩
2023-03-14

x86-64 ABI记录在这里,版本252(我的答案中最新的ABI)可在此处下载。

如果我正确阅读了第21页et-seq,它会说如果sizeof(struct)小于等于8个字节,那么它将在普通寄存器中传递。此后,规则变得复杂,但我认为如果它是9-16字节,它可能会在SSE寄存器中传递。

至于类,记住类和结构之间的唯一区别是默认访问。然而,规则明确指出,如果有一个非平凡的复制构造函数或非平凡的析构函数,该结构将作为隐藏引用传递。

皇甫鸿远
2023-03-14

这里定义了ABI规范。
这里提供了更新的版本。

我假设读者已经习惯了文档中的术语,并且能够对基本类型进行分类。

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

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]
}

< sup >复制省略在这里工作

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

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。

将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);
}

一旦我们有了每个8Bs的类,比如说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是整数,因此前8B是整数b是X87和X87UP,因此第二个8B是内存。最后一节课是两个8B的记忆。

例子

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

最后一堂课是计算出来的。

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

> < li>

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

如果类型具有MEMORY类,则调用者为返回值提供空间,并将此存储的地址传递到%rdi中,就好像它是函数的第一个参数一样。实际上,此地址成为“隐藏”的第一个参数。在返回时,%rax将包含调用者在%rdi中传入的地址。

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

SSE和SSE根据需要设置寄存器< code>xmm0和< code>xmm1。

X87 和 X87UP 寄存器 st0

这里是技术定义。

ABI的定义报告如下。

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

请注意,每个8B都是独立分类的,因此可以相应地通过
特别是,如果没有剩余的参数寄存器,它们可能会在堆栈上结束。

 类似资料:
  • 问题内容: x86-64 SysV ABI除其他事项外,指定如何在寄存器中传递函数参数(在中的第一个参数,然后依次类推),以及如何将整数返回值传递回(对于真正的大值,则传递)。 但是,我找不到的是传递小于64位的类型时参数或返回值寄存器的高位应该是什么。 例如,对于以下功能: … 将被传入和在,但他们只是32位。不要的高32位和需求为零?直观上,我会假设是的,但是所有gcc,clang和icc 生

  • 2. x86的寄存器 x86的通用寄存器有eax、ebx、ecx、edx、edi、esi。这些寄存器在大多数指令中是可以任意选用的,比如movl指令可以把一个立即数传送到eax中,也可传送到ebx中。但也有一些指令规定只能用其中某个寄存器做某种用途,例如除法指令idivl要求被除数在eax寄存器中,edx寄存器必须是0,而除数可以在任意寄存器中,计算结果的商数保存在eax寄存器中(覆盖原来的被除数

  • 我在读《英特尔手册》第2A卷。 将AL、AX、EAX或RAX寄存器中的值与第一个操作数(目标操作数)进行比较。如果两个值相等,则将第二个操作数(源操作数)加载到目标操作数中。否则,目标操作数将加载到AL、AX、EAX或RAX寄存器中。RAX寄存器仅在64位模式下可用。 如果失败,在累加器中加载目标的目的是什么?

  • 问题内容: 我有一些通过JNI调用的C函数,这些函数带有指向结构的指针,还有一些其他函数将分配/释放指向相同类型结构的指针,以便处理包装程序更加容易。令人惊讶的是,JNI文档很少介绍如何处理C结构。 我的C头文件如下所示: 相应的JNI C包装文件包含: …最后是对应的Java类: 不幸的是,此代码在点击后立即使JVM崩溃。我对JNI有点陌生,不知道可能是什么问题。 编辑 :我应该注意,C代码非常

  • 我记得在我的体系结构类中,假设一级缓存命中率为1个周期(即与寄存器访问时间相同),但在现代x86处理器上,这是真的吗? L1缓存命中需要多少个周期?它与注册访问相比如何?

  • 基本上指令有8->16、8->32、8->64、16->32和16->64。 32->64的转换在哪里?我必须使用签名版本吗? 如果是的话,您如何使用完整的64位来表示无符号整数?