假设Linux上的x86-64 ABI,在C ++中的什么条件下结构传递给寄存器中的函数而不是堆栈中的函数?他们在什么条件下返回登记册?答案是否会改变课程?
如果它有助于简化答案,您可以假设一个参数/返回值而没有浮点值。
答案 0 :(得分:11)
我认为读者习惯于文档的术语,并且他们可以对原始类型进行分类。
如果对象大小大于两个八字节,则在内存中传递:
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;
}
8Bs及其领域
First 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
}
8Bs及其领域
First 8B :a
第二个8B :b
a
是SSE,所以第一个8B是SSE
b
是INTEGER,因此第二个8B是INTEGER。
最后的课程是计算出来的。
根据类别返回值:
MEMORY
调用者将隐藏的第一个参数传递给函数,以便将结果存储到
在C ++中,这通常涉及复制省略/返回值优化。
必须将此地址返回到eax
,从而将 MEMORY 类“引用”返回到隐藏的调用者分配的缓冲区。
如果类型具有类MEMORY,则调用者为返回提供空间 value并将该存储的地址传递给%rdi,就像它是第一个一样 函数的参数。实际上,该地址首先变为“隐藏” 论点。 返回时,%rax将包含已传入的地址 调用者在%rdi。
INTEGER 和 POINTER
根据需要注册rax
和rdx
。
SSE 和 SSEUP
根据需要注册xmm0
和xmm1
。
X87 和 X87UP
注册st0
技术定义为here。
ABI的定义见下文。
如果de /构造函数是隐式声明的默认de /构造函数,并且如果:
,则它是微不足道的•它的类没有虚函数,也没有虚基类,而且 •其类的所有直接基类都有简单的de / constructors和
•对于类的所有类型(或其数组)的非静态数据成员,每个这样的类都有一个简单的de / constructor。
请注意,每个8B都是独立分类的,因此每个8B都可以相应地传递 特别是,如果没有剩余的参数寄存器,它们可能会在堆栈中结束。
答案 1 :(得分:4)