无虚方法的类调用成员函数指针为何仍处理虚函数逻辑?
无虚方法的类中调用成员函数指针时,为何仍会处理虚函数逻辑?
示例代码
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
原因解析
这是因为GCC/Clang等编译器遵循的Itanium C++ ABI对成员函数指针采用了统一的设计规范:无论当前类是否包含虚函数,成员函数指针的调用逻辑都必须兼容虚函数的情况,编译器不会为无虚函数的类生成特殊化的调用代码。
具体细节:
- 在Itanium ABI中,成员函数指针本质是一个结构体:对于非虚成员函数,结构体存储函数地址(因函数地址按字节对齐,最低位为0)和
this指针的调整值(示例中为0);对于虚成员函数,会用地址最低位的1作为标记,同时存储虚表偏移等信息。 - 汇编里的
test al,1就是检查函数地址的最低位:如果是1,说明这是一个虚函数指针,需要通过虚表完成寻址;如果是0,则直接跳转到目标函数地址。 - 虽然你的类
A没有虚函数,但编译器生成的call成员函数是通用逻辑——它无法预知mfuncP未来会不会被赋值为某个派生类的虚函数指针(比如假设存在继承A并添加虚函数的派生类B,将B的虚函数指针赋值给A的mfuncP),因此必须保留兼容虚函数的分支逻辑,以保证代码的通用性和正确性。
在你的示例中,mfuncP初始化为非虚函数&A::returnArg,所以虚函数分支实际不会被执行,但编译器依然会生成这段代码。
内容的提问来源于stack exchange,提问作者Cpp Beginner
相关产品推荐
相关产品推荐

