可调用类的成员变量数量的变化如何映射到调用它的函数的汇编?

问题描述 投票:0回答:1

考虑这个 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++ 代码中的差异?

c++ class assembly x86-64 calling-convention
1个回答
0
投票

对于超过 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
最新问题
© www.soinside.com 2019 - 2025. All rights reserved.