用GDB查看vptr需先停在构造完成后,执行x/1gx &obj读取对象首地址的8字节得到vptr值;再用x/5a <vptr值>查看虚表内容,结合info symbol验证函数名,注意-O0编译以避免优化干扰。

用gdb查看运行时虚函数表指针(vptr)的值
虚函数表本身不直接存储在对象内存里,对象开头存放的是指向虚函数表的指针(vptr)。想确认虚函数表布局,第一步是拿到这个指针地址。
在gdb中停在构造完成后的对象位置(比如构造函数返回后),用 p/x &obj 查对象首地址,再用 x/1gx &obj 读取头 8 字节(x86_64 下)——这就是 vptr 的值。
- 如果类有多个虚基类或多重继承,
vptr可能不止一个,分布在对象不同偏移处,需结合类布局分析(如用gdb ptype或class-layout插件) - 优化等级(
-O2)可能导致内联或 devirtualization,vptr 可能根本没被写入,务必用-O0 -g编译调试版本 - 注意:
x/1gx显示的是虚函数表起始地址,不是表内容;下一步要对这个地址做反汇编或内存读取
用gdb读取虚函数表内容并对照符号名
拿到 vptr 指向的地址(比如 0x555555558010)后,虚函数表本质是一组函数指针数组,每个元素是虚函数的地址。用 x/5a 0x555555558010 可以按符号地址格式打印前 5 项(含可能的 RTTI 指针)。
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
- 第一项常为 RTTI type_info 指针(可
x/1gx后再info symbol验证),第二项开始才是虚函数地址(但具体偏移依赖 ABI,Itanium C++ ABI 规定 vtable 前两项为 RTTI + offset-to-top) - 用
info symbol 0x555555558020能反查该地址对应的函数名,确认是否为预期的Base::func()或Derived::func() - 若看到
0x0或非法地址,说明虚函数表未初始化完成(常见于构造函数执行中途)或对象内存已被释放
用objdump查看编译期生成的虚函数表段(.rodata)
编译器在链接阶段会把虚函数表放入只读数据段(通常是 .rodata),可用 objdump -s -j .rodata a.out 提取原始字节,再人工定位。
立即学习“C++免费学习笔记(深入)”;
- 先用
nm -C a.out | grep 'VTT\|vtable'找到类似0000000000004020 V vtable for A的符号,其地址就是虚函数表起始 - 虚函数表项是绝对地址,但 objdump 默认显示相对重定位,加
--no-reloc并配合-d(反汇编)更易读,例如:objdump -d --no-reloc -j .rodata a.out | grep -A10 "4020:" - 注意:模板实例化、inline 函数、未引用的虚函数可能导致对应 vtable 项被丢弃,静态链接时尤其明显
为什么直接看汇编代码很难定位虚函数调用目标
源码中 obj->func() 编译后通常变成三步:读 vptr → 加偏移取函数地址 → 间接跳转(如 jmp QWORD PTR [rax+16])。汇编本身不体现“这是第几个虚函数”,偏移量需对照类定义推算。
- 虚函数序号由声明顺序决定(不是定义顺序),纯虚函数也占槽位,中间不能空缺
- 使用
gdb disassemble看调用点时,重点找mov加载 vptr 和后续基于[reg+imm]的间接调用指令,imm就是字节偏移(如+16表示第三个函数,因每项 8 字节) - Clang 和 GCC 对虚函数表布局基本一致(Itanium ABI),但 MSVC 完全不同(不兼容),跨平台调试时别默认套用 Linux 下的经验
nm 和 objdump 交叉验证符号地址。任何脱离运行时上下文的“理论布局图”都容易在多层继承时失效。

















