成员函数指针不能直接转为 void*,因其在abi中是多字段结构(如msvc下为{addr, offset},itanium下含thunk),语义上无单一地址;安全做法是用std::function+lambda封装。

成员函数指针不能直接转成 void*,这是 C++ 标准明令禁止的
你试图对 &MyClass::func 做 reinterpret_cast<void>(...)</void>?编译器大概率报错或行为未定义。原因很简单:非静态成员函数指针在多数 ABI(如 Itanium C++ ABI、MSVC)中**不是单个地址**,而是一个结构体(通常 8–16 字节),可能包含 this 调整偏移、虚表索引、甚至 thunk 地址。直接取“入口地址”本身在语义上就是模糊的——虚函数?多重继承?带 this 调整的基类函数?这些都会让“一个地址”失去意义。
所以别想着“获取地址”这个动作本身是安全通用的。真正能做的,是根据具体目标场景选择妥协方案:
- 若目标是调试/日志打印,可用
std::printf("%p", static_cast<void>(static_cast<char>(nullptr) + 1))</char></void>这类 trick 避开标准限制(但仅限观察,不可调用) - 若目标是 hook 或 inline patch,必须针对特定编译器+ABI 手动解析成员函数指针布局(例如 MSVC 下前 4 字节常为偏移,后 4 字节为函数地址)
- 若只是想“调用”,老实用对象 + 成员指针语法:
(obj.*ptr)()或(ptr_obj->*ptr)()
MSVC 下解析成员函数指针:看懂 {addr, offset} 二元组
在 MSVC x64 编译器下,非虚、单继承的非静态成员函数指针通常被编码为两个 64 位字段:function_address 和 this_adjustment。你可以用联合体暴力读取:
union MemberFuncPtrRepr {
void (MyClass::*ptr)();
struct {
uint64_t addr;
int64_t offset;
} raw;
};
<p>MemberFuncPtrRepr u{ &MyClass::foo };
printf("raw addr = %p, this offset = %ld\n",
reinterpret_cast<void>(u.raw.addr), u.raw.offset);
</void></p>
注意三点:
-
u.raw.addr是真实函数入口(即 .text 段地址),可传给GetProcAddress对比或用于 detour -
u.raw.offset是 this 指针需调整的字节数(正数向高地址偏移,负数向低地址),调用前必须手动加到 this 上 - 一旦涉及虚函数或菱形继承,该 layout 失效;
sizeof(void (MyClass::*)())可能为 16 字节,此时 union 无法安全映射
Itanium ABI(GCC/Clang)下:函数指针藏在第二字段,且需处理 thunk
在 Linux/macOS 的 GCC/Clang 中,成员函数指针是 16 字节结构:{ptr_to_function, adjustment},但 ptr_to_function **不一定是最终函数地址**——它可能是跳转到 thunk 的地址,thunk 再修正 this 并跳转到真实函数。你可以这样提取:
auto ptr = &MyClass::bar; uint64_t* raw = reinterpret_cast<uint64_t>(&ptr); uintptr_t func_addr = raw[1]; // 注意:Itanium ABI 中函数地址在第二个 8 字节 </uint64_t>
但风险极高:
- 如果函数是虚函数,
raw[1]指向的是 vtable 条目,不是代码段地址 - 如果编译器启用了
-frecord-gcc-switches或 LTO,成员函数指针可能被进一步优化成间接引用 - thunk 地址本身可能被 PIC 重定位,硬编码该地址会导致运行时崩溃
真正安全的替代方案:用 std::function + lambda 封装,放弃裸地址
99% 的实际需求(比如注册回调、存入容器、跨模块传递)根本不需要裸地址。用 lambda 捕获对象和成员函数,转成 std::function<void></void>,既类型安全又 ABI 无关:
MyClass obj;
auto fn = [&obj]() { obj.func(); }; // 或 [ptr = &MyClass::func, &obj]() { (obj.*ptr)(); }
std::function<void> callback = fn;
// 后续可自由拷贝、存储、调用
callback();
</void>
如果你非要“地址”用于调试输出,可以用 std::hash<:function>></:function> 或自定义小对象标记,而不是强求机器码地址。毕竟,C++ 的抽象价值,正在于让你不必和 this 调整、vtable 偏移、thunk 跳转这些细节搏斗。
真正难的从来不是怎么取地址,而是想清楚:你拿到那个数字之后,到底打算拿它干什么。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











