考虑这个 TU:
struct Foo {
int operator()(int) const;
};
int foo(Foo b) {
return b(17);
}
它将编译为:
foo(Foo):
sub rsp, 24
mov esi, 17
lea rdi, [rsp+15]
call Foo::operator()(int) const
add rsp, 24
ret
而如果类包含 6 个
int
成员,就像这样,
struct Baz {
int i;
int j;
int k;
int u;
int v;
int z;
int operator()(int) const;
};
然后与上面相同的
foo
将编译为
baz(Baz):
sub rsp, 8
mov esi, 17
lea rdi, [rsp+16]
call Baz::operator()(int) const
add rsp, 8
ret
如何将程序集中的差异映射到 C++ 代码中的差异?
对于超过 16 个字节的成员,x86-64 System V 将其传递到堆栈上,因此
baz
可以仅将指向其堆栈参数的指针作为 this
的 operator()
指针传递。 C++ 成员函数将 T* this
作为其第一个参数,因此这是 RDI 中的 LEA,第一个“正常”参数位于第二个参数传递槽 RSI 中。 (实际上是 ESI,因为在此 ABI 中 int
是 32 位。)
(有趣的事实:Windows x64 通过指针传递较大的对象,因此每个 arg 都适合 8 字节堆栈(或 reg)槽。)
成员数为零时,没有寄存器参数可以溢出,因此
foo
只是将指针传递给它自己保留的 1 个字节的堆栈空间。 (请注意,在调用推送 8 字节返回地址之前,sub rsp,24
保留了比需要多 16 个字节,而不是仅使用必须保留的 8 个字节中的一些来保持 RSP % 16 == 0
对齐。这是 GCC 长期遗漏的优化。)
大小在 1 到 16 字节之间(并且仍然没有复制构造函数或析构函数,可以让 C++ ABI 将其传递到内存中),您会看到将传入的 RDI(可能还有 RSI,具体取决于大小)转储到的代码堆栈并传递一个指向该堆栈的指针。
struct Bar {
int i[2];
int operator()(int) const;
};
int bar(Bar b) {
return b(17);
}
bar(Bar):
sub rsp, 24 # reserve space
mov esi, 17
mov QWORD PTR [rsp+8], rdi # store incoming register arg to memory
lea rdi, [rsp+8] # pass a pointer to it as Bar* this
call Bar::operator()(int) const
add rsp, 24
ret