不能。dladdr仅根据地址查符号区间,不理解C++名字修饰,无法保证返回函数指针对应的原始符号名;需满足非inline、非static、未hidden、-rdynamic、未strip等条件才可能成功。

dladdr 能否直接获取函数指针的符号名?
不能。dladdr 接收的是地址(const void*),它查的是该地址落在哪个共享对象的哪个符号范围内,但不保证返回的就是“你期望的那个函数名”。尤其当函数被内联、优化掉、或属于模板实例化/lambda 时,dladdr 返回的 dli_sname 往往是空、指向父作用域(如 _ZSt3getIiE...)、或干脆是最近的非内联函数入口——不是你传入的函数指针所指向的原始声明名。
关键判断点:dladdr 只做“地址到符号区间”的静态映射,不理解 C++ 名字修饰(mangling)语义,也不跟踪编译器生成的符号别名关系。
如何让 dladdr 返回有意义的函数名?
必须满足三个硬条件:
- 函数不能被
inline、static或__attribute__((visibility("hidden")))修饰;否则符号不会进入动态符号表 - 编译时需保留符号信息:
-rdynamic(等价于-Wl,--export-dynamic),否则主可执行文件的全局符号对dladdr不可见 - 链接时不能 strip:
strip ./a.out会清空.dynsym和.symtab,导致dladdr查不到任何符号
示例验证流程:
void my_api() { }
int main() {
Dl_info info;
if (dladdr((void*)&my_api, &info) && info.dli_sname) {
printf("symbol: %s\n", info.dli_sname); // 输出 "my_api"(未 mangling)
}
}
注意:my_api 必须是非 static 全局函数,且编译命令含 g++ -rdynamic main.cpp -ldl。
backtrace + dladdr 组合为什么常返回 ??:0?
这是最常踩的坑:backtrace 拿到的是栈帧返回地址(即 call 指令下一条指令的地址),而 dladdr 对该地址查到的符号,通常是调用点所在函数(caller),不是被调用函数(callee)本身。更糟的是,若该地址落在函数 prologue 或 epilogue 区域(比如刚进函数还没到第一条有效指令),dladdr 就无法匹配到符号起始位置,返回空 dli_sname,最终显示 ??:0。
可靠做法是:对每个 backtrace() 返回的地址,先减去 1(退回到 call 指令位置),再传给 dladdr;但这仍不保真,尤其有 tail-call 优化时会彻底失效。
真正可用的组合逻辑:
- 用
backtrace获取地址数组 - 对每个地址调用
dladdr,检查info.dli_saddr—— 它是符号的实际起始地址 - 仅当
addr == info.dli_saddr时,才认为该地址精确对应符号入口(此时dli_sname才可信) - 否则跳过,或 fallback 到
addr2line -e ./a.out 0x...(需带调试信息)
C++ 函数指针与符号名之间没有稳定映射
这是根本限制:C++ 标准不规定函数指针必须携带 RTTI 或符号名,编译器也无义务维护这种关联。即使你用 reinterpret_cast 得到地址,dladdr 的结果仍取决于链接时符号导出策略、优化等级(-O2 下 std::vector::push_back 可能压根没独立符号)、以及运行时加载器是否重定位成功。
如果业务强依赖“函数指针 → 可读名”,唯一可控路径是手动注册映射表:
std::map<void std::string> func_name_map = {
{(void*)&foo, "foo"},
{(void*)&bar, "bar"}
};</void>
自动方案目前只有基于调试信息的解析(如 libdw、libbfd)或编译期反射(C++23 std::source_location 仅限当前函数,不解决指针反查)。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











