c++oding="utf-8" ?>
成员函数指针不能直接reinterpret_cast为void*,因其在Itanium/MSVC等ABI中是含this偏移、vtbl索引等的多字段结构(8–16字节),强转会丢失元信息导致未定义行为或崩溃。

成员函数指针不能直接 reinterpret_cast 成 void*
这是最常踩的坑:试图用 reinterpret_cast 获取“真实地址”,结果得到的是未定义行为,甚至编译失败。C++ 标准明确禁止将成员函数指针转为对象指针类型——因为成员函数指针在多数 ABI(如 Itanium C++ ABI、MSVC)中根本不是单个地址,而是包含this偏移、函数地址、甚至虚表索引的多字节结构(常见 8–16 字节)。直接强转会丢弃关键元信息,调用时必然崩溃。
用 union 拆解成员函数指针的原始字节(仅限特定 ABI)
若你控制编译环境(如 x86_64 Linux + GCC/Clang),且目标是调试或 hook,可借助 union 将成员函数指针按底层字节解释。前提是确认 ABI 布局 —— Itanium ABI 中,非虚成员函数指针通常前 8 字节是函数地址,后 8 字节是 this 调整偏移(对普通类为 0):
union MemberFuncPtrRepr {
void (MyClass::*mf)();
struct { uint64_t addr; int64_t offset; } raw;
};
MemberFuncPtrRepr u{&MyClass::foo};
uintptr_t entry_addr = u.raw.addr; // 这才是实际代码入口
- 仅适用于无虚继承、无虚函数的普通成员函数
- MSVC 的布局完全不同(可能含 thunk 地址),此法在 Windows 上大概率失效
- 必须用
-fno-rtti和一致的 ABI 编译,否则运行时可能因 vtable 插入而错位
获取虚函数的实际地址需绕过 vtable 查找
对于虚函数,&MyClass::virt_func 得到的仍是“虚调用描述符”,不是最终函数地址。真实地址藏在对象的 vtable 中:
MyClass obj; void** vtable = *reinterpret_cast<void>(&obj); // 获取 vtable 首地址 // 假设 virt_func 是第 2 个虚函数(索引 1),则: uintptr_t vfunc_addr = reinterpret_cast<uintptr_t>(vtable[1]); </uintptr_t></void>
- vtable 偏移依赖编译器和继承链,需用
offsetof(MyClass, _vptr)或调试器确认 - Clang/GCC 可能将 vtable 放在对象开头,但 MSVC 在多重继承下可能不保证
- 该地址仍可能指向 thunk(如用于 this 调整),需反汇编验证是否为最终目标
更安全的做法:用 std::function + lambda 封装后取地址(仅限静态可调用)
如果你真正需要的是“能被通用函数指针调用的地址”,而非原始机器码地址,应放弃裸指针操作,改用标准方式封装:
MyClass obj;
auto bound = [&obj]() { obj.foo(); };
std::function<void> f = bound;
// 然后通过平台 API(如 dlsym/dlopen)或调试符号获取其内部函数地址 —— 但注意:这仍是实现定义行为,不可移植
</void>
- 标准库不提供访问
std::function内部函数指针的接口,GCC libstdc++ 的_M_functor是私有实现 - 这种做法本质是逃避问题:你真正要的往往不是“地址”,而是“可注入/可 patch 的执行点”,此时应优先考虑 inline hook 库(如 Microsoft Detours、mhook)而非手撕指针
成员函数指针的“地址”概念本身是模糊的——它依赖对象状态、继承关系和 ABI 实现。硬解包容易出错,而绕过抽象层直接操作,往往意味着你已经站在 undefined behavior 的边缘。真正稳定的方案,从来不是强转,而是接受 C++ 的抽象边界,用虚函数机制或回调设计替代裸地址需求。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











