成员函数指针不能直接转为void*,因其非普通地址而含this偏移、虚表索引等额外信息;x64下常占8或16字节,需用union等底层技巧提取code字段,但仍须正确处理this调整,否则导致未定义行为或崩溃。

成员函数指针不能直接转 void*,这是 C++ 标准明令禁止的
试图用 reinterpret_cast<void>(&MyClass::func)</void> 编译会失败(GCC/Clang 报错 invalid cast),MSVC 可能静默接受但行为未定义。根本原因在于:非静态成员函数指针不是普通地址,它可能包含额外信息——比如 this 指针偏移、虚表索引或 thunk 跳转地址,具体取决于编译器实现和类布局(单继承/多重继承/虚继承)。
实际中,sizeof(MyClass::*) 常为 8 或 16 字节(x64 下),远大于普通函数指针的 8 字节,这本身就说明它不是裸地址。
想获取真实跳转入口?得绕过类型系统,用汇编级技巧提取
在 x86-64 Linux(GCC/Clang)且无虚函数、无多重继承的简单类中,成员函数指针底层通常是一个结构体:{ code_ptr, adjustor }。你可以用联合体(union)暴力读取其首字段:
union MemberFuncPtrUnion {
void (MyClass::*mf)();
struct { void* code; ptrdiff_t adj; } raw;
};
MemberFuncPtrUnion u{ &MyClass::foo };
void* entry = u.raw.code; // 这才是 CPU 实际跳转的目标地址
注意:entry 是函数第一条指令的地址,但不等于“可直接调用的函数指针”——因为调用约定要求 this 参数必须正确传入,而 entry 本身不携带 this 调整逻辑。若类有虚函数或继承关系复杂,u.raw.code 可能指向一个 thunk,里面含 add rdi, imm 等调整指令。
立即学习“C++免费学习笔记(深入)”;
- 仅适用于 POD-like 成员函数(无虚、无多重继承、无虚基类)
- 不同编译器 ABI 不同:Itanium ABI(GCC/Clang)和 MSVC ABI 结构不兼容
- 开启 LTO 或 -O2 后,编译器可能内联或优化掉该函数,
entry失效
真正安全的“地址等价”操作:只用于调试或 hook 场景,且必须绑定对象实例
如果你目标是 hook 成员函数或打日志,不要执着于“纯地址”,而是构造一个可调用对象:
- 用
std::function<void></void>包装&MyClass::func,再通过target_type()和target()提取原始指针(但仍是类型安全封装,不暴露地址) - 在 Linux 上用
LD_PRELOAD+dlsym(RTLD_NEXT, "_ZN7MyClass4funcEv")替换符号(需知道 mangled 名) - Windows 上用 Microsoft Detours 或 MinHook,它们内部处理了 thiscall 调用约定和指针解包
硬解成员函数指针到地址的唯一合理场景,是写调试器或逆向分析工具——此时你已控制整个进程,且明确知道目标平台 ABI 和编译选项。
容易被忽略的陷阱:this 指针调整永远比“地址”更重要
即使你成功拿到 entry,直接用 reinterpret_cast<void>(entry)(obj)</void> 调用,在多重继承下大概率 crash。因为:
- 成员函数指针里的
adjustor字段告诉编译器:“调用前要把 this 加上 X 偏移” - 这个偏移在编译期确定,但运行时无法从
entry反推 - 虚函数调用更复杂:entry 可能是 vtable 中的 slot,实际地址要查表 + 偏移
所以,任何脱离成员函数指针原类型、强行转成裸函数指针的尝试,本质上都是在和 ABI 博弈——赢了省几行代码,输了就是 undefined behavior 和偶发崩溃。


















