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类没有虚函数,因此不清楚为什么要进行这种处理。
必须考虑调用为
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)。