动态库如何避“坑”

陈静
原创 1443       2019-06-14  


吴霸哥:有时候我觉得,代码就像我们的娃,一颗赤子之心让我们笑看生活,真的很奇妙。


瓜仔:是啊。你看,那么多美丽的BUG等我们去发现。天天有惊喜。很刺激呢。


吴霸哥:我说认真的,你看就拿编译来说,你知道编译分为静态编译和动态编译,二者脾性各有不同。


静态编译是个强迫症,啥都要加载,一个都不放过。

动态编译就很灵活,很有打算,运行时用到哪个模块就调用哪个,看起来活得也比较轻松。


瓜仔:SO?很萌吗?我不喜欢熊孩子…尤其是那种老报错的!


吴霸哥:对,你看动态链接库(.so),萌中带皮,你知道里面动态库中,编译、运行时的报错都如何避免吗?


瓜仔:哈?……请BUG…不是,霸哥明示吧就!


吴霸哥展开了如下一段直击心灵的讲解:


所谓的“库”,从本质上来说就是一种可执行代码的二进制格式,可以被载入内存中执行。库又分静态库和动态库两种。

静态库包含在编译时静态绑定到一个程序的函数。动态库则不同,它是在加载应用程序时被加载的,而且它与应用程序是在运行时绑定的。


这里重点讲解的是动态库的一些“坑”。


静态编译


我们先说(·a),编译器在编译可执行文件的时候,将可执行文件需要调用的对应静态库(.a)中的部分提取出来,链接到可执行文件中去,使可执行文件在运行的时候不依赖于动态链接库。



静态编译方式如下:


1. 编译生成目标文件

$ g++ -c print.cpp    

2. 创建静态库

$ar rcs libprint.a print.o

3. 链接静态链接库,生成可执行文件

$g++ main.cpp -L. -lprint -o test

4. 运行测试

$ ./test

hundsun

5. 删除静态库后运行,

 $ rm libprint.a

 $./test

hundsun

 

动态编译


再说(.so),动态编译的可执行文件需要附带动态链接库(.so),在执行时,需要调用其对应动态链接库中的命令。


优点:

  • 缩小了执行文件本身的体积;

  • 加快了编译速度;

  • 节省了系统资源;


缺点:

  • 哪怕是很简单的程序,只用到了链接库中的一两条命令,也需要附带一个相对庞大的链接库;

  • 如果其他计算机上没有安装对应的运行库,则用动态编译的可执行文件就不能运行;


print.cpp文件和main.cpp文件代码同上。动态编译方式如下:


1. 编译成动态链接库

$ g++ print.cpp -fPIC -shared -o libprint.so

2. 链接动态库,生成可执行文件

$ g++ main.cpp -L. -lprint -o test

3.运行测试

$ ./test

hundsun

4.删除动态库后运行,可执行程序无法正常运行

$ rm libprint.so

$ ./test

./test: error while loading shared libraries: libprint.so: cannot open shared object file: No such file or directory

 


链接多个库,多个库的函数是同一个

 

 

1. 编译成动态链接库

$ g++ print1.cpp -fPIC -shared -o libprint1.so

2. 链接动态库,生成可执行文件

$ g++ main.cpp-L. -lprint –lprint1 -o test

3. 运行测试

$ ./test

hundsun

4. 调换链接顺序,链接动态库,生成可执行文件

$ g++ main.cpp -L. –lprint1 –lprint -o test

5. 运行测试

$ ./test

hundsun1


由此可见,动态库名字相同的话,运行的时候,用的是第一个链接的库。


这里我们再做一个实验,把第一个链接的库删了。

$ g++ main.cpp -L. –lprint1 –lprint -o test

$ rm libprint1.so

$ ./test 

hundsun

从上面的实验可以看出,程序可以正常运行。其打印出来的结果是第二个链接的库。原因是实际的符号定位是在运行期进行的。


 符号未定义


undefined reference to XXX


1. 编译成动态链接库,编译成功

$ g++ print1.cpp -fPIC -shared -o libprint.so

2. 链接动态库,生成可执行文件

$ g++ main.cpp -L. -lprint -o test

/libprint.so: undefined reference to `show_table'

collect2: ld returned 1 exit status

发现编译时直接报错。


虽然show_table函数并没有实现,但是在编译libprint.so的时候也没有报错。因为这个时候libprint.so是一个动态库,这个里面存在一些没有实现的符号,是可以在编译二进制文件或者运行的时候指定的,为了印证这个结论,我们新增show.cpp文件。


1. 链接动态库,生成可执行文件

$ g++ main.cpp -o test -L. -lprint –lshow

发现编译可执行文件成功了!!!

2. 执行测试

$ ./test

show_table

hundsun

结果完全正确!!!


undefined symbol XXX

在前面的基础上,如果我们把show.cpp代码成下面的代码,再重新编译show.so。


a. 重新编译show.so

$ g++ show.cpp -fPIC -shared -o libshow.so

b. 再执行测试

$ ./test

./test: symbol lookup error: libprint.so: undefined symbol: show_table

可以发现在执行test的时候,会报错libprint.so有未定义的符号show_table。

 

总结


业务部门在用开发工具开发业务系统的时候,一个项目工程会有多个业务模块,每个业务都会编译成一个so。业务模块间有函数调用的场景,若动态库的so版本不一致,启动时就会报错了,如A.so调用了B.so的函数func,若func的函数名或者参数变了,B.so重新编译了,而A.so没有重新编译,程序起来的直接报未定义的符号,此时则需要把A.so对应的模块代码重新生成,重新编译。


恒生技术之眼原创文章,未经授权禁止转载。详情见转载须知

联系我们

恒 生 技 术 之 眼