函数名本身就是函数入口地址,但受增量链接、ASLR等机制影响,调试器显示地址与printf打印地址可能不一致;类成员函数地址不可直接强转获取,需通过虚表或调试接口安全提取。

函数名本身就能当地址用,但直接取值可能不是你调试器里看到的那个——这不是 bug,是链接器和运行时机制在起作用。
函数名就是地址,但得看怎么用
对普通非成员函数(比如 int foo() { return 42; }),&foo、foo、reinterpret_cast<void>(foo)</void> 都能拿到入口地址,类型上等价于 void(*)()。但注意:
- VS 默认开启增量链接(
/INCREMENTAL)时,foo可能指向一个跳转桩(thunk),真实代码在别处;调试器悬停显示的是符号解析后的 RVA + 基址,而printf("%p", foo)打印的是跳转桩地址 - 禁用增量链接(项目属性 → 链接器 → 常规 → 启用增量链接 = 否)后,两者才一致
- GCC/Clang 下通常没这问题,除非用了
-fPIE或-shared,此时地址是运行时重定位后的
类成员函数地址不能直接强转成 void*
&MyClass::bar 的类型是 void (MyClass::*)(),它不是裸地址,而是编译器实现相关的结构(可能含 this 偏移、vtbl 索引等)。直接 reinterpret_cast<void>(&MyClass::bar)</void> 会得到无意义值,甚至编译失败。
- 想获取虚函数实际代码地址:先取对象虚表指针,再读对应索引项,如
*(uintptr_t**)obj >> 0得 vtable,再((uintptr_t*)vtable)[index] - 非虚成员函数可借助内联汇编(MSVC):
_asm { mov eax, offset MyClass::bar },但仅限 x86 且不支持 x64 - 跨平台安全做法:用 GDB / LLDB 查,如
info address MyClass::bar或info line <行号>
std::function 无法可靠提取底层函数地址
std::function 是类型擦除容器,内部可能存裸函数指针、lambda 闭包、绑定对象等。调用 target<T*>() 仅在构造时传入的是匹配的函数指针才有效,其他情况返回 nullptr。
立即学习“C++免费学习笔记(深入)”;
- 传入普通函数:可用
func.target<int(*)()>()获取,但签名必须完全一致(含 const/volatile) - 传入 lambda(即使无捕获):底层是闭包类型,
target查不到函数指针,只能拿到闭包对象地址,不代表可执行代码位置 - 成员函数绑定(
std::bind(&C::f, obj)):同理,不可逆向还原为成员函数地址
真正需要“真实地址”时,优先走符号调试接口
如果你在做 hook、热补丁或 JIT 相关开发,靠 &func 拿到的地址很可能不是最终执行位置。更可靠的方式是:
- Windows:用 DIA SDK 查询符号 RVA,再加模块基址;或用
VirtualQuery确认内存页属性(是否可执行) - Linux/macOS:用
dladdr获取符号地址,配合/proc/self/maps校验段权限 - 调试阶段:GDB 中
p &func和info symbol 0x...联合使用,比代码里硬取更可信
最常被忽略的一点:函数地址不是静态常量,它依赖加载基址、ASLR、链接选项——任何假设“地址永远不变”的逻辑,在真实部署环境里大概率出错。


















