为什么即使类没有虚方法,对成员函数指针的调用也会处理虚函数?

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

https://godbolt.org/z/W8b3TG5f6

struct A {
    int __attribute__((noinline)) call(int a) {
        return (this->*mfuncP)(a);
    }

    int __attribute__((noinline)) returnArg(int a) {
        return a;
    }

    int (A::*mfuncP)(int) = &A::returnArg;
};

int test(int a) {
    return A().call(a);
}

产量

A::returnArg(int):
        mov     eax, esi
        ret
A::call(int):
        mov     rax, QWORD PTR [rdi]
        add     rdi, QWORD PTR [rdi+8]
        test    al, 1
        je      .L4
        mov     rdx, QWORD PTR [rdi]
        mov     rax, QWORD PTR [rdx-1+rax]
.L4:
        jmp     rax
test(int):
        sub     rsp, 24
        mov     esi, edi
        mov     rdi, rsp
        mov     QWORD PTR [rsp], OFFSET FLAT:A::returnArg(int)
        mov     QWORD PTR [rsp+8], 0
        call    A::call(int)
        add     rsp, 24
        ret

如果我理解正确的话,在这种情况下,程序集是这样的:如果函数地址的 LSB 设置为 1,则它被解释为引用虚拟方法(https://itanium-cxx-abi.github)。 io/cxx-abi/abi.html#member-function-pointers)因此需要分支。然而,A类没有虚函数,因此不清楚为什么要进行这种处理。

c++ assembly gcc
1个回答
4
投票

必须考虑调用为

virtual
的可能性,因为实际上并不要求
int (A::*)(int)
类型的成员指针引用类
A
的成员。唯一适用的限制是它必须指向
A
的直接成员、
A
的(直接或间接、非虚拟且明确的)基类或派生类(直接或间接、非虚拟)并且明确地)来自
A
。当
.*
->*
用于将成员函数指针绑定到对象时,(仅)其 most-driven 对象必须包含(直接或间接)所引用的成员。 (并且直接包含该成员的类在最派生的对象中不应有歧义,请参阅 open CWG 2593。)

例如,以下定义了行为,

test
必须产生
42
,为此调用机制必须考虑间接虚拟调用:

struct B : A {
    virtual int vfunc(int) { return 0; };
};

struct C : B {
    int vfunc(int) override { return 42; }
};

int test(int a) {
    C c;
    c.mfuncP = static_cast<int (A::*)(int)>(&B::vfunc);
    return c.call(a);
}

如注释中所述,如果将

A
标记为
final
,那么编译器可以推断
mfuncP
不能引用
virtual
成员函数。

GCC 执行该优化,但由于某种原因仍然添加

this
指针偏移量,在调用中保证为零,因为
A
也没有任何基类。

Clang 目前似乎根本没有执行该优化。


但是,在使用此类强制转换时,请记住,虽然标准定义了行为,但 MSVC 的 ABI 使用已知不支持此功能的成员指针表示形式。它将产生警告并错误编译代码。我认为有一些标志可以使其行为符合标准(但也可能破坏 ABI)。

© www.soinside.com 2019 - 2024. All rights reserved.