PAYATHON 2026

如何获取非静态成员函数地址并用 dladdr 定位动态库?

数据小陈

结论

没有可移植的写法能把 &A::g 直接转换成 dladdr 所需的 void*。&A::g 的类型是成员函数指针 void (A::*)(),与普通函数指针不同。实现可能会在成员函数指针中保存 this 指针调整信息;对于虚函数,还可能需要额外的派发信息。因此,Clang 会拒绝 reinterpret_cast<void*>(&A::g)。

如果只想查询某个动态库的地址,可以在库中定义一个普通包装函数,再把它的地址传给 dladdr。这样可以找到包装函数所在的库,但不能保证得到内联成员函数某个特定机器码副本的地址。

使用普通包装函数查询动态库

例如,可以在要查询的共享库中定义一个导出的 C 包装函数:

#include <dlfcn.h>
#include <iostream>

struct A {
    void g() {}
};

extern "C" __attribute__((visibility("default"), noinline))
void A_g_probe(A* object) {
    object->g();
}

int main() {
    Dl_info info{};
    if (dladdr(reinterpret_cast<void*>(&A_g_probe), &info) != 0) {
        std::cout << info.dli_fname << '\n';
    }
}

在 Linux 常见的工具链约定下,可以像示例中那样把普通函数指针转换为 void*,传给 dladdr。请确保包装函数编译进要查询的共享库。查询结果表示包装函数地址属于哪个库。如果 g 的实现也在同一库中,通常可以据此确认库;但结果本身不能证明 g 是从该库调用或取用的。

内联函数的限制

示例中的 g() 在类定义中实现,属于内联定义。编译器可以在调用处展开它,也可能在不同共享库中生成不同副本。因此,g 不一定有唯一且稳定的机器码地址可供查询。即使某个 ABI 允许用特定方式从成员函数指针中提取地址,这也不是可移植的 C++ 做法,并且可能在虚函数场景或使用不同编译器、编译选项时失效。

如果要让实现归属于某个明确的共享库,可以把 g 的定义移出类定义,放在该库的实现文件中,再通过库内的包装函数查询地址。如果要追踪实际调用点或内联后的代码位置,单靠 dladdr 和 &A::g 无法给出通用且可靠的答案,需要使用针对具体编译器和构建方式的调试或分析手段。

备注:内容仅供参考。