函数名直接使用(如add)即为其运行时机器码起始地址,但需确保函数已导出且未被内联;dlsym是获取外部共享库中函数地址的标准方法,而dladdr仅用于反查已知地址的符号信息。

函数指针本身的地址(即指针变量在内存中的位置)和它所指向的函数地址是两回事。你真正想问的,几乎总是「如何获取某个函数在运行时的真实机器码起始地址」——比如用于调试、符号回溯、热补丁或与 dlsym 配合使用。这个地址不是编译期常量,尤其在启用 ASLR 或链接进共享库时,必须运行时查询。
函数名直接用就是函数地址,但有前提条件
在绝大多数普通场景下,add(不带括号)就等价于 &add,两者都给出函数 add 的地址:
int add(int a, int b) { return a + b; }-
auto p1 = add;和auto p2 = &add;类型一致,都是int(*)(int,int) - 但该地址仅在当前翻译单元可见;若函数定义在 .so 中且未导出(如未加
__attribute__((visibility("default")))),链接器可能将其设为 local symbol,dlsym就查不到 - 内联函数(
inline)、模板实例化函数、未显式实例化的函数模板,通常无法取地址——编译器可能根本没生成独立符号
dladdr 能查到函数地址,但只适用于已加载且有符号表的模块
dladdr 是 GNU libc 提供的运行时符号反查工具,但它查的是「给定内存地址对应哪个符号+所在模块」,反过来用(先有函数名,再查地址)并不直接。常见误用是传入函数指针值却期望返回符号名——这只有在该函数确实存在于动态符号表中才有效:
- 确保目标函数在编译时导出:GCC 加
-fvisibility=default,或函数声明加__attribute__((visibility("default"))) - 调用前必须保证模块已由
dlopen(NULL, ...)或主程序自身加载(NULL表示主可执行文件及所有已加载共享库) - 示例代码中常写
dladdr((void*)some_func, &info),但如果some_func是 static 函数或被 LTO 优化掉,info.dli_sname会是nullptr -
info.dli_fbase是模块基址,info.dli_saddr是符号地址(注意:它和你传入的(void*)some_func可能不完全相等,尤其存在函数别名或版本符号时)
用 dlsym 获取函数地址才是生产环境的标准做法
当函数位于外部共享库(.so)中,且你只有函数名字符串(比如配置驱动、插件系统),dlsym 是唯一可靠方式:
- 先
void* handle = dlopen("libmath.so", RTLD_LAZY);,失败时检查dlerror() - 再
auto func_ptr = reinterpret_cast<int>(dlsym(handle, "add"));</int>—— 必须显式reinterpret_cast,因为dlsym返回void* - 如果函数签名不匹配(比如参数个数/类型错),调用时行为未定义,不会编译报错
- 注意:C++ 函数名经过 mangling,
dlsym查的是 C 链接名;C++ 函数需用extern "C"包裹声明才能被dlsym正确识别
容易被忽略的兼容性陷阱
跨平台或长期维护项目里,这几个点实际踩坑率极高:
-
dladdr不是 POSIX 标准,macOS 上可用_dyld_lookup_and_bind替代,但接口完全不同;Windows 用GetProcAddress+GetModuleHandle - Clang/GCC 在 -O2 下可能将短函数内联,导致原函数符号消失;加
__attribute__((noinline))强制保留符号 - 使用
std::function存储函数指针看似方便,但它不是函数指针——不能传给要求int(*)(int,int)的 C 接口,也不能被dlsym直接赋值 - 从
dlsym得到的地址,若对应函数后续被dlclose卸载,再调用就是野指针;务必管理好 handle 生命周期
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











