https://mp.weixin.qq.com/s/wJ8BZZQKSZTtPJGvQjuRxQ
最近在工作上遇到的一个小问题:在我们的一个项目中使用系统库定义的strcpy函数有一些问题,于是一个同事自定义了一个strcpy函数,函数签名与系统库的定义完全一样,成功在使用strcpy的地方用自定义实现替换了系统库的实现。
原本是一件不起眼的小事,但是稍加思考后会发现,其中有一些细节值得探究:
1.自定义函数与系统库定义的函数同名为什么不会冲突;
2.为什么在调用函数的地方会调用到自定义的函数。
为了解答自己的疑惑,验证自己的猜想,所以做了一些小实验。
推理
经过一番短暂的思考,我认为这个问题需要从编译和链接这两个过程所做的工作来分析,我猜想原因大致如下:
1. 系统库都是预先编译好的,对用户只提供头文件和so共享库文件。用户在自己的代码中定义了和系统库同名的函数,编译时并不会出现两个同名函数的定义,所以编译器不会报多重定义的错误;2. 若是在两个so共享库都有相同函数签名的函数定义,链接时会选择第一个出现的定义,这和链接的顺序有关系;3. 若是在我们自己的代码中定义了一个和系统库同名的函数,由于先编译我们的代码,之后才会链接系统库,所以,在任何使用到该函数的地方就会直接引用我们的定义,链接时并不会去链接系统库。
验证
为了验证上述推理,我们做几个小实验。
定义两个共享库lib1和lib2,这两个库都只有一个函数echo,但实现不同。具体代码如下:
// lib1.h
#ifndef TEST_LIB1_H
#define TEST_LIB1_Hextern int echo(int x);#endif // TEST_LIB1_H
// lib1.cpp
#include "lib1.h"int echo(int x) { return x; }
// lib2.h
#ifndef TEST_LIB2_H
#define TEST_LIB2_Hextern int echo(int x);#endif // TEST_LIB2_H
// lib2.cpp
#include "lib2.h"int echo(int x) { return x + 1; }
再定义一个主函数,调用echo。
// main.cpp
#include <iostream>#include "lib1.h"
#include "lib2.h"using namespace std;int main() {cout << echo(3) << endl;return 0;
}
将lib1和lib2编译成共享库。
foo@bar:~$ g++ lib1.cpp -fPIC -shared -o liblib1.so
foo@bar:~$ g++ lib2.cpp -fPIC -shared -o liblib2.so
将主文件编译成可执行程序并链接共享库,调整共享库的链接顺序,会得到不同的结果。
foo@bar:~$ g++ main.cpp -L -llib1 -llib2
foo@bar:~$ ./a.out
3
foo@bar:~$ g++ main.cpp -L -llib2 -llib1
foo@bar:~$ ./a.out
4
若是在主文件中再定义一个同名函数,并重复上述过程,将得到不同结果。
// main.cpp
#include <iostream>#include "lib1.h"
#include "lib2.h"using namespace std;int echo(int x) { return x + 2; }int main() {cout << echo(3) << endl;return 0;
}
foo@bar:~$ g++ main.cpp -L -llib1 -llib2
foo@bar:~$ ./a.out
5
通过几个简单的小实验,成功验证了上述的猜想是正确的。
总结
在工作中遇到的小问题,认真去思考也能探索出很多细节。当然,一切的根源还是自己对编译链接理解得不够深入。所谓勿在浮沙筑高楼,这些最基本的知识,是一切上层建筑的根基。
原创 读书小窗前