最近项目遇到了一些问题。场景如下:主程序依赖两个库libA的funcA函数和libB的funcB函数。原理图代码(main.cpp)如下:includeintfuncA(int,int);intfuncB(int,int);intmain(){printf("%d,",funcA(2,1));printf("%d\n",funcB(2,1));return0;}libA(libA.cpp)的实现示意图如下:intsubfunc(inta,intb){returna+b;}intfuncA(inta,intb){returnsubfunc(a,b);}libB(libB.cpp)的原理图实现如下:intsubfunc(inta,intb){returna-b;}intfuncB(inta,intb){returnsubfunc(a,b);}可以看出,funcA调用了libA中的内部函数subfunc,funcB调用了libB中的内部函数subfunc。这两个子函数有不同的实现,但不幸的是它们不小心重名了。我们尝试编译运行:g++-fPIClibA.cpp-shared-olibA.sog++-fPIClibB.cpp-shared-olibB.sog++main.cpplibA.solibB.so-omainexportLD_LIBRARY_PATH=。./main我们的预期结果是3,1(funcA和funcB各自调用了不同的subfunc实现),但实际结果是3,3(funcA和funcB都调用了libA中的subfunc实现)原因是我们通过readelf查看符号:$readelf-alibA.so|grepsubfunc000000200a60000200000007R_X86_64_JUMP_SLO00000000000000708_Z7subfuncii+02:00000000000070820FUNCGLOBALDEFAULT10_Z7subfuncii402000070400000070LOBALDEFAULT10_Z7subfuncii$readelf-alibB.so|grepsubfunc000000200a60000200000007R_X86_64_JUMP_SLO0000000000000708_Z7subfuncii+0?2:000000000000070822FUNCGLOBALDEFAULT10_Z7subfuncii45:000000000000070822FUNCGLOBALDEFAULT10_Z7subfuncii可见libA和libB里面都有subfunc符号,名字Exactlythesame,andtheyareallGLOBAL.GLOBAL符号是全局符号。具有相同名称的全局符号将被视为相同的符号。由于main先加载libA,因此它获取libA中的subfunc符号,然后加载libB。libB中的子函数忽略了解决方案。这实际上是一个符号可见性(SymbolVisibility)问题。既然有GLOBAL符号,自然就会有LOCAL符号。LOCAL符号仅在当前库中可见,而不是全局可见。如何将符号更改为本地?最直接的方法是添加隐藏可见性的标志。修改后的libA.cpp:__attribute__((visibility("hidden")))intsubfunc(inta,intb){returna+b;}intfuncA(inta,intb){returnsubfunc(a,b);}重新编译再次执行,结果为3,1,成功!在这里再次检查libA的符号:$readelf-alibA.so|grepsubfunc40:00000000000006a820FUNCLOCALDEFAULT10_Z7subfuncii可以看到subfunc符号变成了LOCAL,默认为LOCAL。上面的方法可以解决问题,但是实际情况往往是,libA内部的函数很多,对外暴露的却很少。你能指定一些符号为GLOBAL,而其他符号为LOCAL吗?答案是肯定的,修改libA.cpp如下:intsubfunc(inta,intb){returna+b;}__attribute__((visibility("default")))intfuncA(inta,intb){returnsubfunc(a,b);此时需要在libA的编译参数中加入-fvisibility=hidden:g++-fPIClibA.cpp-shared-fvisibility=hidden-olibA.so也可以解决问题。跨平台兼容性Windows平台对符号有不同的行为。Windows中的默认动态库符号是本地的。GLOBAL符号是通过__declspec(dllexport)声明的,因此可以使用以下方法来实现兼容性:如果定义了_WIN32||defined__CYGWIN__#ifdefBUILDING_DLL#ifdef__GNUC__#defineDLL_PUBLIC__attribute__((dllexport))#else#defineDLL_PUBLIC__declspec(dllexport)//注意:实际上gcc似乎也支持这种语法。#endif#PUb__de#UC____atf__GNute((dllimport))#else#defineDLL_PUBLIC__declspec(dllimport)//注意:实际上gcc似乎也支持这种语法。#endif#endif#defineDLL_LOCAL#else#if__GNUC__>=4#defineDLL_PUBLIC__attribute__((visibility("default")))#defineDLL_LOCAL__attribute__((visibility("hidden")))#else#defineDLL_PUBLIC#defineDLL_LOCAL#endif#endif隐藏外部依赖符号我遇到的实际情况比上面的复杂,subfunc不是在libA中实现的,而是在另一个外部库libsubfunc.a中实现的。libA通过包含头文件来获得这个函数:include"subfunc.h"intfuncA(inta,intb){returnsubfunc(a,b);}以上-fvisibility只对实现有效,对声明无效.但是libsubfunc.a是一个第三方库,我们不能更改它的代码或者它的头文件,对于这种情况,gcc提供了如下的支持方式:pragmaGCCvisibilitypush(hidden)#include"subfunc.h"#pragmaGCCvisibilitypopintfuncA(inta,intb){returnsubfunc(a,b);}这种方法比较方便灵活。如果dlopen这样加载,可以通过参数来控制。
