需先确认类含虚函数,再通过对象首地址获取虚表指针;虚函数索引按声明顺序从0开始编号,索引值乘指针大小得字节偏移;调用时须用匹配的成员函数指针类型转换后执行。

在C++逆向分析、ABI兼容性调试或自定义虚函数调用场景中,需要精确知道某个虚函数在虚表(vtable)中的索引位置及对应函数指针的原始地址,而非通过对象实例间接调用。
确认目标类是否含虚函数并定位虚表起始地址
编译器只为含虚函数的类生成虚表,且虚表指针(vptr)固定位于对象内存布局的最开头(x86-64下为8字节)。先用 sizeof 和 offsetof 验证:
定义一个带虚函数的测试类:struct Base { virtual ~Base() = default; virtual void foo() {} virtual int bar() { return 42; } };
声明实例 Base b;,然后强制取其首地址:void** vtable = *static_cast<void>(&b);</void> —— 这一步必须确保 b 已构造完成,【未构造对象直接解引用vptr会导致段错误】。
立即学习“C++免费学习笔记(深入)”;
计算虚函数在虚表中的偏移索引
虚表是函数指针数组,每个元素大小等于平台指针宽度(通常为8字节)。索引从0开始,顺序与虚函数在类中首次声明顺序严格一致(不考虑继承重写后的覆盖关系)。
第一步:写出类中所有虚函数的完整声明序列(含析构函数),按源码出现顺序编号;
第二步:数出目标函数是第几个——例如 foo() 在 Base 中是第二个虚函数(析构函数为第0个,foo() 为第1个,bar() 为第2个);
第三步:该索引值即为虚表内偏移量,乘以 sizeof(void*) 得字节偏移。所以 foo 的虚表项地址为 vtable + 1(指针算术,自动按 sizeof(void*) 缩放)。
提取虚函数原始函数指针并安全调用
方法一:直接读取虚表项内容
void* func_ptr = vtable[1]; // 获取 foo 的函数地址
方法二:转换为正确签名后调用(关键!必须匹配原函数类型)
using FooFunc = void (Base::*)(); auto pfoo = reinterpret_cast<foofunc>(func_ptr); (b.*pfoo)();</foofunc> —— 注意这里不能用普通函数指针类型,必须是成员函数指针类型,否则调用时 thiscall 约定会出错。
方法三(更底层):绕过成员函数指针封装,手动传入 this 指针并跳转
using RawFunc = void (*)(Base*); auto raw = reinterpret_cast<rawfunc>(func_ptr); raw(&b);</rawfunc> —— 这种方式仅在无虚基类、单继承且编译器未插入额外 this 调整代码时可靠;【clang/gcc -fno-vtable-verify 或 -O2 下可能因 this 调整失败】。


















