在日常开发工作中,经常会遇到符号重复定义的问题,这篇文章主要描述的是链接器在处理重复符号时的规则。
在解析引用的过程中,最常发生的问题就是会出现重复符号,该问题发生在链接的最后阶段,具体为在所有可用符号列表中包含两个甚至更多同名符号,连接器就会报错,一般如下错误:
./libsecond.a(secondfile.o):在函数‘duplicateFunction(int)’中:
/root/test/cf/secondfile.cpp:6: duplicateFunction(int) 的多重定义
./libfirst.a(firstfile.o):/root/test/cf/firstfile.cpp:6:第一次在此定义
判断相同的符号,在C和C++有些不同:
下面两个文件 firstfile.cpp
和secodfile.cpp
分别编译成静态库,再链接到可执行程序中。
void firstFunction();
void duplicateFunction(int a);
#include
void firsFunction() {
std::cout<<"first function"<<std::endl;
}
void duplicateFunction(int a) {
std::cout<<"duplicate function in first file "<<a<<std::endl;
}
gcc -Wall -g -c firstfile.cpp
ar -rcs libfirst.a firstfile.o
void secondFunction();
void duplicateFunction(int b);
#include
void secondFunction() {
std::cout<<"second function"<<std::endl;
}
void duplicateFunction(int b) {
std::cout<<"duplicate function in secode file "<<b<<std::endl;
}
gcc -Wall -g -c secondfile.cpp
ar -rcs libsecond.a secondfile.o
#include
#include "firstfile.h"
#include "secondfile.h"
int main() {
duplicateFunction(0);
secondFunction();
return 0;
}
g++ main.cpp -L. -lfirst -lsecond -o app
报错:
./libsecond.a(secondfile.o):在函数‘duplicateFunction(int)’中:
/root/test/cf/secondfile.cpp:6: duplicateFunction(int) 的多重定义
./libfirst.a(firstfile.o):/root/test/cf/firstfile.cpp:6:第一次在此定义
程序app链接libfirst.a
和libsecond.a
,链接静态库不允许有重复符号。
链接器在处理动态库的符号链接时,比静态库复杂些,对重复符号并非是零容忍,所以即使多个动态库中有相同的符号,链接器也有一套规则选定一个符号,而非直接报错。
对通过dlopen
动态加载的方式并没重复符合的问题。
对静态链接的方式链接动态库处理重复符号为以下规则:
处理重复符号是通常不考虑静态符号(static
方法或变量),无论符号属于客户二进制文件还是属于静态链接的动态库。
改例子中包含 一个静态库,一个动态库和客户程序,静态库与动态库中有一个同名的函数。
int staticLibFirstFunction(int x);
int staticLibSecondFunction(int x);
int sharedStaticDuplicateFunction(int x);
#include
int staticLibFirstFunction(int x){
std::cout<<__FUNCTION__<<std::endl;
return x+1;
}
int staticLibSecondFunction(int x){
std::cout<<__FUNCTION__<<std::endl;
return x+2;
}
int sharedStaticDuplicateFunction(int x){
std::cout<<"staticlib:"<<__FUNCTION__<<std::endl;
return 0;
}
gcc -Wall -g -c staticlib.cpp
ar -rcs libstaticlib.a staticlib.o
int shlibFunction(void);
int sharedStaticDuplicateFunction(int x);
#include
int shlibFunction(void) {
std::cout<<__FUNCTION__<<std::endl;
return 0;
}
int sharedStaticDuplicateFunction(int x) {
std::cout<<"sharedLib:"<<__FUNCTION__<<std::endl;
return 0;
}
gcc -Wall -g -c shlib.c
gcc -fIPC -shared shlib.o -Wl,-soname,libshlib.so.1 -o libshlib.so.1.0.0
ldconfig -n .
ln -s libshlib.so.1 libshlib.so
ldconfig -n
是让ldconfig
创建所需动态库版本的软连接(这里是libshlib.so.1,因为soname
是这个)。
#include "staticlib.h"
#include "shlib.h"
int main() {
staticLibFirstFunction(0);
staticLibSecondFunction(1);
shlibFunction();
sharedStaticDuplicateFunction(2);
return 0;
}
gcc -Wall -g -I…/ -c main.c
gcc main.o -L…/ -lstaticlib -L…/sharedlib -lshlib -Wl,-R./ -o app
-Wl,-R 是给链接器传递设在rpath值,用于运行时定义依赖的动态库的路径,可以通过readelf -d的查看DT_RPATH/RPATH值。
运行结果
staticLibFirstFunction
staticLibSecondFunction
shlibFunction
staticlib:sharedStaticDuplicateFunction
对于在静态库和动态库中都定义了的符号sharedStaticDuplicateFunction
,链接器找的是静态库中的符号。因为静态库中的符号就相当于是可执行程序的符号,是第一优先级符号。
不同动态库的ABI符号冲突,两个动态库中有同名的导出符号,根据前面描述的优先级规则,两个动态库的符号都属于第二优先级,链接器将根据静态链接时的库的顺序选择一个。
int shlibFunction(void);
int firstshlibFunction(void);
#include
int shlibFunction(void) {
std::cout<<"firstshlib:"<<__FUNCTION__<<std::endl;
return 0;
}
int firstshlibFunction(void) {
std::cout<<__FUNCTION__<<std::endl;
return 0;
}
gcc -Wall -g -c -fPIC firstshlib.cp
gcc -shared firstshlib.o -Wl,-soname,libfirstshlib.so.1 -o libfirstshlib.so.1.0.0
ldconfig -n .
ln -s libfirstshlib.so.1 libfirstshlib.so
动态库libfirstshlib.so
,导出的符号为shlibFunction
,firstshlibFunction
。
int shlibFunction(void);
int secondshlibFunction(void);
int secondshlibAnotherFunction(void);
#include
int shlibFunction(void) {
std::cout<<"secondshlib:"<<__FUNCTION__<<std::endl;
return 0;
}
int secondshlibFunction(void) {
std::cout<<__FUNCTION__<<std::endl;
return 0;
}
int secondshlibAnotherFunction(void) {
shlibFunction();
return 0;
}
gcc -Wall -g -fPIC -c secondshlib.cpp
gcc -shared secondshlib.o -Wl,-soname,libsecondshlib.so.1 -o libsecondshlib.so.1.0.0
ldconfig -n .
ln -s libsecondshlib.so.1 libsecondshlib.so
动态库libsecondshlib.so
,导出的符号为shlibFunction
, secondshlibFunction
,secondshlibAnotherFunction
。其中shlibFunction
为重复符合。
#include
#include "firstshlib.h"
#include "secondshlib.h"
int main() {
shlibFunction();
firstshlibFunction();
secondshlibFunction();
secondshlibAnotherFunction();
return 0;
}
g++ -Wall -g -I./firstshlib -I./secondshlib -c main.cpp
g++ main.o -L./ -lfirstshlib -lsecondshlib -Wl,-R./ -o app
最后生成程序app
时,首先链接的是libfirstshlib.so
,所以输出结果如下:
firstshlib:shlibFunction
firstshlibFunction
secondshlibFunction
firstshlib:shlibFunction
动态中重复的符号shlibFunction
,最终选择的是libfirstshlib.so
的符号。
对函数动态库libsecondshlib.so
中的函数secondshlibAnotherFunction()
,在实现的内部调用的是重复符号shlibFunction()
,它最终也是链接的libfirstshlib.so
中的符号。
如果将链接顺序改为:g++ main.o -L./ -lsecondshlib -lfirstshlib -Wl,-R./ -o app
,输出如下:
secondshlib:shlibFunction
firstshlibFunction
secondshlibFunction
secondshlib:shlibFunction
通过这个例子就可以说明,同优先级的符号,由链接时的顺序决定。
void shlibFunction();
void firstshlibFunction();
#include
void shlibFunction() {
std::cout<<"firstshlib:"<<__FUNCTION__<<std::endl;
}
void firstshlibFunction() {
std::cout<<__FUNCTION__<<std::endl;
}
gcc -Wall -g -fPIC -c firstshlib/firstshlib.cpp
gcc -shared firstshlib.o -Wl,-soname,libfirstshlib.so.1 -o libfirstshlib.so.1.0.0
ldconfig -n .
ln -s libfirstshlib.so.1 libfirstshlib.so
动态库libfisrtshlib.so
的导出符号为shlibFunction
, firstshlibFunction
。
void secondshlibFunction();
#include
static void shlibFunction() {
std::cout<<"secondshlib,static function:"<<__FUNCTION__<<std::endl;
}
void secondshlibFunction() {
shlibFunction();
}
gcc -Wall -g -fPIC -c secondshlib/secondshlib.cpp
gcc -shared secondshlib.o -Wl,-soname,libsecondshlib.so.1 -o libsecondshlib.so.1.0.0
ldconfig -n .
ln -s libsecondshlib.so.1 libsecondshlib.so
动态库libsecondshlib.so
的导出符号为secondshlibFunction
,它还有一个内部符号shlibFunction
,被static
修饰。它与libfirstshlib.so
中的导出符号shlibFunction
重名。
#include "firstshlib.h"
#include "secondshlib.h"
int main() {
shlibFunction();
firstshlibFunction();
secondshlibFunction();
}
g++ -Wall -g -l./fisrtshlib -l./secondshlib -c main.cpp
g++ main.o -L./ -lfirstshlib -lsecondshlib -Wl,-R./ -o app
运行app
,输出入下:
firstshlib:shlibFunction
firstshlibFunction
secondshlib,static function:shlibFunction
可执行程序app
中的shlibFunction
方法是链接的firstshlib
中的方法,而secondshlibFunction
中调用的shlibFunction
链接的是libsecondshlib.so
中的自己的内部方法,其实在生成libsecondshlib.so
动态库时就确定了。在生成执行文件时,内部符号并不参与链接。
上面就是连接器链接动态库时,处理重复符号的规则,这套规则虽然能避免链接出错,但也可能会导致链接了错误的同名符号而导致逻辑的问题。
一般在开发阶段,应该尽可能的采用独立的命名规则来规避符合重复的问题,特别是在C++中通过命名空间的机制,可以很大程度的避免重复符号。但是对规模庞大,依赖第三方库的应用来说,重复符号的问题难以控制。那么就需要我们能知道这套规则,尽可能的使链接器能链接到正确的符号。
在下一篇文章中,将介绍这套规则可能会导致的问题及解决方法。