成员函数指针不是地址而是编译器私有结构体,仅当类无虚函数、无虚基类、单继承且关闭rtti时,在itanium abi下才可能通过union或memcpy提取其函数入口虚拟地址。

要在C++中获取并打印类成员函数的内存虚拟地址,必须绕过标准类型系统限制,直接解析成员函数指针的底层二进制布局——因为 &T::func 本身不是地址,而是一个编译器私有结构体,直接 reinterpret_cast
确认目标函数是否为非虚、无继承、无RTTI的平凡成员函数
这一步决定后续所有操作是否可能成功。只有满足 【无虚函数、无虚基类、单继承(或无继承)、编译时关闭RTTI(-fno-rtti)】 四个条件,成员函数指针才可能在GCC/Clang Itanium ABI下表现为“前8字节≈函数入口地址”的可解析结构。若类含 virtual ~Base() 或使用了 virtual inheritance,此路径立即失效。
写一个空 struct A { void f() {} }; 编译时加 -fno-rtti -O0,用 sizeof(&A::f) 验证:若结果为 8,则继续;若为 12 或 16,说明 ABI 已插入偏移/索引字段,不可靠提取。
用 union 拆解成员函数指针原始字节(Itanium ABI 专用)
方法一:适用于 GCC/Clang x86_64 Linux/macOS,且已确认 sizeof(void (A::*)()) == 8
定义 union 将成员函数指针与 uint64_t 对齐:
union { void (A::*mp)() = &A::f; uint64_t raw; } u;
直接取 u.raw 即为该函数在 .text 段的虚拟地址(需验证);printf("entry = %p\n", reinterpret_cast
⚠️ 注意:u.raw 是整数,不是指针,必须显式 cast 为 void* 才能被 %p 正确打印;若不 cast,%p 会截断高32位(在 LP64 下出错)。
方法二:兼容 12 字节 Itanium 布局(前8字节为函数地址,后4字节为 this 调整量)
union { void (A::*mp)(); struct { uint64_t addr; int32_t adj; } raw12; } u12{&A::f};
printf("addr = %p, adj = %d\n", reinterpret_cast
若 adj 为 0,且 addr 指向可执行内存页(可用 mincore() 验证),则 addr 极大概率是真实入口。
用 reinterpret_cast + memcpy 提取原始字节(跨编译器通用试探法)
第一步:声明成员函数指针变量,并确保其未被内联或优化掉
volatile auto ptr = &A::f; // volatile 阻止编译器折叠符号
第二步:取其地址,转为 const char*,按 sizeof 精确读取
const char* raw = reinterpret_cast
第三步:逐字节打印十六进制值,观察模式
for (size_t i = 0; i (raw[i])); printf("\n");
在 GCC 12 x86_64 -fno-rtti 下,你很可能看到前8字节是类似 00 00 00 00 40 12 00 00 的小端格式地址(对应 0x401200);后4字节全零。这个 0x401200 就是你要的虚拟地址。
验证地址有效性:检查是否落在 .text 段且可执行
第一步:获取当前进程的内存映射信息(Linux)
FILE* maps = fopen("/proc/self/maps", "r");
第二步:逐行扫描,匹配地址所在行是否含 r-xp 标志
例如:00400000-00401000 r-xp 00000000 08:01 123456 /path/a.out → 表明 0x400000~0x401000 可执行
第三步:若你提取的 addr(如 0x401200)落在某段 r-xp 区间内,则它是真实函数入口;否则可能是 thunk 地址或无效值。
这一步不能跳过:很多情况下提取出的数值指向 .plt 或 stub,调用会 segfault。
对虚函数:必须通过对象实例读取 vtable 条目
第一步:构造一个已完全初始化的对象
A obj;
第二步:获取其虚表指针(假设单继承、vptr 在对象起始)
void** vtbl = *reinterpret_cast
第三步:确定目标虚函数在虚表中的索引(必须静态已知)
① 编译时用 clang++ -cc1 -fdump-record-layouts A 查看虚函数顺序;
② 或运行时用 GDB:p/x *(void**)(&obj),再 p/x $1[0]、$1[1]…比对符号名;
第四步:读取对应槽位地址并打印
printf("vfunc entry = %p\n", vtbl[0]);
注意:vtbl[0] 不一定是第一个声明的虚函数——析构函数常占首位,顺序依赖 ABI 和编译器版本。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











