假设Linux上的x86-64 ABI,在C语言中,在什么条件下结构被传递给寄存器中的函数,而不是堆栈中的函数?在什么条件下,它们会在登记簿中返回?答案会随着班级而改变吗?
如果它有助于简化答案,则可以假定只有一个参数/返回值,并且没有浮点值。
x86-64 ABI记录在这里,版本252(我的答案中最新的ABI)可在此处下载。
如果我正确阅读了第21页et-seq,它会说如果sizeof(struct)小于等于8个字节,那么它将在普通寄存器中传递。此后,规则变得复杂,但我认为如果它是9-16字节,它可能会在SSE寄存器中传递。
至于类,记住类和结构之间的唯一区别是默认访问。然而,规则明确指出,如果有一个非平凡的复制构造函数或非平凡的析构函数,该结构将作为隐藏引用传递。
这里定义了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是SSEb
是整数,因此第二个8B是整数。
最后一堂课是计算出来的。
这些值将相应地返回给它们的类:
> < li>
内存< br >调用方将一个隐藏的第一个参数传递给函数,以便将结果存储到该函数中。< br >在C语言中,这通常涉及复制省略/返回值优化。这个地址必须返回到< code>eax中,从而“通过引用”将内存类返回到一个隐藏的调用方分配的缓冲区。
如果类型具有MEMORY类,则调用者为返回值提供空间,并将此存储的地址传递到%rdi中,就好像它是函数的第一个参数一样。实际上,此地址成为“隐藏”的第一个参数。在返回时,%rax将包含调用者在%rdi中传入的地址。
整数和指针
根据需要注册 rax
和 rdx
。
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缓存命中需要多少个周期?它与注册访问相比如何?
我相信我了解linux x86-64 ABI如何使用寄存器和堆栈将参数传递给函数(参见前面的ABI讨论)。我感到困惑的是,在函数调用中是否/哪些寄存器应该保留。也就是说,哪些寄存器被保证不被破坏?