不能安全获取成员函数指针的原始地址——它本质是编译器私有的多字段结构(含this偏移、vtbl索引等),reinterpret_cast转uintptr_t属未定义行为,仅特定ABI下非虚单继承场景可近似提取,但不可移植。

一、使用 reinterpret_cast 强制转换获取成员函数指针的原始字节值
在标准 C++ 中,成员函数指针并非简单地址,其底层可能包含 this 偏移、虚表索引或调整量等信息;但某些编译器(如 MSVC、GCC 在特定 ABI 下)允许通过 reinterpret_cast 将其转为足够宽的整数类型以观察原始位模式。该操作不具可移植性,但可用于调试与逆向分析。
1、定义一个含非虚、非静态成员函数的类,并声明其成员函数指针类型。
2、使用 decltype 获取该成员函数指针类型,例如:using MFPtr = void (MyClass::*)();
3、将成员函数指针变量用 reinterpret_cast 转换为 uint64_t(确保宽度覆盖指针大小),例如:uint64_t raw = reinterpret_cast
立即学习“C++免费学习笔记(深入)”;
4、输出 raw 值,观察其十六进制表示,注意:此数值不代表可直接调用的代码地址,尤其在存在多重继承或虚继承时可能为负偏移或带标志位。
二、借助编译器内置函数 __builtin_funcptr_to_int(GCC/Clang)提取底层整数表示
GCC 与 Clang 提供非标准扩展函数 __builtin_funcptr_to_int,可安全地将成员函数指针映射为其 ABI 对应的整数位模式,避免 reinterpret_cast 可能引发的未定义行为(如严格别名违规)。该函数返回值类型与目标平台指针宽度一致。
1、确保编译器为 GCC 或 Clang,且启用 C++11 或更高标准。
2、声明成员函数指针变量 mfptr,例如:auto mfptr = &MyClass::process;
3、调用内置函数获取整数:auto val = __builtin_funcptr_to_int(mfptr);
4、将 val 转为 uint64_t 并打印,此时 val 的低 32 位在 Itanium ABI 下常为 this 调整量,高 32 位可能为函数地址或 vtable 偏移。
三、通过汇编内联读取成员函数指针解引用后的实际跳转目标
当成员函数为非虚且无继承调整时,其指针在多数 ABI 中实际指向一条跳转指令(jmp/call)或函数入口;可通过内联汇编在运行时解析该跳转目标地址。该方法绕过 C++ 类型系统,直探机器码层面。
1、声明成员函数指针并确保其已初始化,例如:MFPtr p = &MyClass::calc;
2、使用 reinterpret_cast 将 p 转为 void** 类型指针(因某些 ABI 将其存储为双字结构)。
3、在支持 x86-64 的 GCC 中,插入内联汇编:asm volatile ("movq (%0), %%rax" : : "r"(ptr) : "%rax"); 其中 ptr 指向成员函数指针内存首地址。
4、将 %rax 值读回 C++ 变量,若该值为 jmp 指令地址,则需进一步反汇编其机器码以提取 rel32 目标;若为直接函数地址,则该值即为真实入口。
四、利用调试符号与 objdump 定位成员函数在 ELF 文件中的符号地址
成员函数在编译后具有独立符号名(经 C++ name mangling),其链接地址可在可执行文件或共享库中直接查得。该方法不依赖运行时指针值,而是从静态二进制视角确认函数物理位置。
1、编译源码时添加 -g 选项保留调试信息,并禁用优化(-O0)以避免内联导致符号丢失。
2、使用 c++filt 解析 mangled 名称,例如:c++filt "_ZN7MyClass5printEv"
3、运行 objdump -t ./a.out | grep "printEv" 提取对应符号的虚拟地址(VMA)。
4、在 GDB 中设置断点于该符号地址并检查 disassemble,该地址即为函数在内存加载后的绝对入口,与成员函数指针是否可直接跳转无关,但可作为验证基准。
五、通过虚表(vtable)手动索引获取虚成员函数的实际地址
对于虚函数,其地址不直接存于成员函数指针中,而位于对象实例的虚表内;可通过读取对象头指针 + vtable 偏移,再解引用虚表项获得真实函数地址。该过程高度依赖 ABI 实现细节。
1、创建类实例 obj,并确保其类型含有虚函数,例如:MyDerived d;
2、获取对象内存起始地址 reinterpret_cast
3、将该位置读为 void** 类型,得到 vtable 地址;根据虚函数在类定义中的声明顺序,计算索引(如首个虚函数索引为 0)。
4、读取 vtable[index] 的值,即为该虚函数的真正入口地址,注意:该地址可能被编译器生成的 thunk 包裹,需检查其前几字节是否为 jmp 指令。


















