题目
编写程序,揭示 C++ 虚函数机制背后的底层实现机制:虚函数表(vtable)与虚表指针(vptr)。通过 sizeof 比对无虚函数类(0 虚表开销)与含虚函数类(首部植入 8 字节 vptr 指针)的对象内存大小;并使用底层指针强转操作(uintptr_t**)提取出对象首地址处存储的 vptr 虚表地址,进一步索引出虚表中的函数指针并完成手工直接调用。
输入格式:无需外部输入。
输出格式:输出类对象内存尺寸对比、vptr 虚表地址提取与通过函数指针直接调用的回显。
数据范围与约定:底层 ABI 内存布局、vptr 植入、虚表函数指针数组。
思路与算法
1. vptr 的植入位置
只要类中声明了至少一个 virtual 函数,编译器就会在每个对象的头部(通常是 offset 0 处)插入一个隐式指针成员 void** __vptr(在 64 位系统下占用 8 字节)。
2. vtable 是全局只读数据
同一类的所有实例对象,其内部的 vptr 均指向同一张属于该类的全局只读虚表(vtable)。vtable 本质上是一个存储成员函数入口地址的指针数组。
3. 多态调用的汇编本质p->foo() 在底层等价于:①从 p 首地址取出 vptr;②通过 vptr[index] 取出函数指针;③跳转执行 call (*funcPtr)(p)。
完整程序
#include <iostream>
#include <cstdint>
class PlainClass {
int a;
int b;
};
class PolymorphicClass {
public:
int a;
int b;
virtual void funcA() {
std::cout << " [底层执行] PolymorphicClass::funcA() 被成功调用!\n";
}
virtual void funcB() {
std::cout << " [底层执行] PolymorphicClass::funcB() 被成功调用!\n";
}
virtual ~PolymorphicClass() = default;
};
// 定义普通函数指针类型
typedef void (*FuncPtr)(PolymorphicClass*);
int main() {
std::cout << "--- 1. 对象内存尺寸对比 (64位系统) ---\n";
std::cout << "无虚函数类 PlainClass 大小: " << sizeof(PlainClass) << " 字节 (2 个 int)\n";
std::cout << "含虚函数类 PolymorphicClass 大小: " << sizeof(PolymorphicClass)
<< " 字节 (vptr 8 字节 + 2 个 int 8 字节)\n\n";
// 2. 深入内存探测 vptr
PolymorphicClass obj;
obj.a = 100;
obj.b = 200;
std::cout << "--- 2. 提取对象头部的 vptr 虚表指针 ---\n";
uintptr_t* vptr = *(reinterpret_cast<uintptr_t**>(&obj));
std::cout << "vptr 是否为空: " << (vptr == nullptr ? "是" : "否") << "\n";
// 虚表中第 0 个槽位是 funcA,第 1 个槽位是 funcB
std::cout << "\n--- 3. 绕过正常调用,纯靠虚表指针直接调用虚函数 ---\n";
FuncPtr fA = reinterpret_cast<FuncPtr>(vptr[0]);
fA(&obj); // 手工传 this 指针直接触发调用
return 0;
}运行示例
输出:
--- 1. 对象内存尺寸对比 (64位系统) ---
无虚函数类 PlainClass 大小: 8 字节 (2 个 int)
含虚函数类 PolymorphicClass 大小: 16 字节 (vptr 8 字节 + 2 个 int 8 字节)
--- 2. 提取对象头部的 vptr 虚表指针 ---
vptr 是否为空: 否
--- 3. 绕过正常调用,纯靠虚表指针直接调用虚函数 ---
[底层执行] PolymorphicClass::funcA() 被成功调用!