C/C++多个链接库含有同名函数,编译会报错吗

  • 起因
  • 基本概念
  • 同名函数测试
    • 测试1:`.o`目标文件
    • 测试2:静态库
    • 测试3:动态库
  • 同名函数的应用

起因

由于业务需要,我司使用了Mellanox某闭源C++程序,Mellanox推荐的定制化开发方法是:对其链接的动态库进行定制化开发,以添加额外的功能。

在方案讨论阶段,发现很多同事对动态库/静态库所代表的的含义并不十分清楚,特别是当同名函数存在时,编译、链接、运行的结果是什么也没有明确的认识,故写下这篇文章。

基本概念

程序函数库可分为下面几种类型:

  1. 静态函数库(static libraries):在编译期间(compile-time)静态链接库会全部拷贝进编译对象中,一般以.a文件的存在
  2. 动态函数库(shared libraries):在程序启动的时候加载到程序中,它可以被不同的程序共享,一般以.so文件存在
    • 动态加载函数库(dynamically loaded libraries),在进程运行期间,使用dlfcn.h中的函数加载、调用、关闭动态库

关于动态库和静态库的优缺点,相关文章很多,这里不再赘述

同名函数测试

使用两个.c文件test2.ctest2.c包含同名函数void test()

// test1.c
#include <stdio.h>void test() {printf("call from test1.c");
}
// test2.c
#include <stdio.h>void test() {printf("call from test2.c");
}

含有main函数的文件main.c

// main.c
extern void test();
int main() {test();
}

测试1:.o目标文件

使用如下命令行,将test2.ctest2.c生成目标文件,并编译可执行文件

gcc -c ./test1.c
gcc -c ./test2.c
gcc -o main ./test1.o ./test2.o ./main.c

结果报错:

./test2.o: In function `test':
test2.c:(.text+0x0): multiple definition of `test'
./test1.o:test1.c:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status

可见,将包含同名函数的目标文件进行链接,如果其在同一个命名空间中,会报multiple definition错误。

测试2:静态库

使用如下命令行编译静态库libtest1.alibtest2.a

g++ -c ./test1.c
g++ -c ./test2.c
ar crv libtest1.a test1.o
ar crv libtest2.a test2.o

接着我们链接编译:

gcc -L. ./main.c -ltest1 -ltest2 -o main

可以编译成功,无报错。执行结果如下

$ LD_LIBRARY_PATH=. ./main
call from test1.c

有朋友会问:“为什么没有报错呢?我明明把包含同名函数的两个静态库链接进同一个可执行文件了。”

为了探究为什么没有报错,我们增加ld选项-Wl,--verbose来看看链接时到底发生了什么。再执行编译,我们得到输出:

...attempt to open ./libtest1.so failed
attempt to open ./libtest1.a succeeded
(./libtest1.a)test1.o
attempt to open ./libtest2.so failed
attempt to open ./libtest2.a succeeded...

可以发现,最终的链接结果,输出的二进制文件只链接了libtest1.a背后的test1.o文件,而没有链接libtest2.a。编译器这么做的含义是:

  1. 编译器根据链接先后顺序,依次查找链接库。
  2. 首先查找libtest1.a,发现其有main函数需要的函数void test(),因此将其进行了链接。
  3. 再扫描到libtest2.a的时候,由于void test()已经被libtest1.a中的符号提供,因此不再链接。

Stack Overflow中有个问题也谈到了这点。

如果使用ld参数--whole-archive强行链接libtest1.alibtest2.a,我们会看到和测试1同样的报错:

$ gcc -L. ./main.c -Wl,--whole-archive -ltest1 -ltest2 -Wl,--no-whole-archive -o main
./libtest2.a(test2.o): In function `test':
test2.c:(.text+0x0): multiple definition of `test'
./libtest1.a(test1.o):test1.c:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status

测试3:动态库

使用如下命令行编译动态库libtest1.solibtest2.so并编译可执行文件。

gcc -shared -fPIC -o libtest1.so test1.c
gcc -shared -fPIC -o libtest2.so test2.c
gcc -L. ./main.c -ltest1 -ltest2 -o main

编译无报错,ldd检查,libtest1.solibtest2.so确实都链接进main可执行文件中。执行结果如下:

$ LD_LIBRARY_PATH=. ./main
call from test1.c

可见,在动态链接时,不同的链接库可以有同名函数,不影响编译。这是由动态链接库的性质决定的,其只有在运行时才会动态加载,并且加载的顺序是由编译时链接的顺序决定的。这也就说符号会以第一个查找到的为准(Symbols are resolved on a first match basis)。

我们也可以通过设置LD_PRELOAD,提前将某动态库load进内存。

同名函数的应用

有朋友会提出这样的疑问,上面虽然做了这么多实验,但多少有点语言律师的感觉,这些知识能改善我们日常生活吗?日常工作中能用的到吗?答案当然是能用得到。

最简单的应用场景,比如某开源库中有个函数我不喜欢,我想写个自己的版本替换掉,那么完全可以利用上述的知识,将自己实现的某函数以动态或者静态的方式链接进可执行文件中,替换自己不喜欢的版本。

工业上常见的应用有以下几种:

  1. 替换库:大名鼎鼎的tcmalloc就是以这种方式运行的。我们将tcmalloc链接进程序,只要tcmalloc库的查找顺序优先于libc,就可以替换原生的内存管理函数为tcmalloc版本。
  2. mock测试:陈硕在文章中详述了如何在C++单元测试中mock系统调用。其中的链接期垫片 (link seams)方法,就是利用libc一般情况下是动态链接的特性,在进程中mock系统调用。

C/C++多个链接库含有同名函数,编译会报错吗相关推荐

  1. DG备库执行只读open的时候报错

    10.2.0.4RAC到单机--DG备库执行只读open的时候报错 SQL> alter database open read only2 ; alter database open read ...

  2. java 静态链接_java如何调用静态链接库里面的函数?

    我现在有静态链接库文件:libtest.a里面有很多.o目标文件,其中一个目标文件在linux下使用nm命令查看其中包含的部分函数:kmer_counter.o:UA_memcpyU__cxa_ate ...

  3. Linux下动态链接库与静态链接库(编写、编译)

    这里写目录标题 函数库的概念 动态库的编译 静态库的编译 动态库相对于静态库的优点和缺点 优点 缺点 函数库的概念 函数库一般分为静态库和动态库两种. 静态库: 是指编译链接时,把库文件的代码全部加入 ...

  4. conda tensorflow_TensorFlow笔记1——补充附录(1) 机器学习相关库安装、使用中遇到的报错情况...

    本文内容汇总 Anaconda(python3.7)和原生python3.7安装tensorflow 警告 AVX2 FMA 报错 'tensorflow' has no attribute 'xxx ...

  5. 本地厂库上传到码云报错fatal: remote origin already exists.

    本地厂库上传到码云时候报错 . 试了好多次,没成功 有原因是密码错了,但是我也没改过密码,以前搞的都是直接再仓库克隆的. 解决方法: 先把仓库清理了 $ git remote rm origin 再链 ...

  6. Python安装库较慢问题,Python报错pip._vendor.urllib3.exceptions.ReadTimeoutError: HTTPSConnectionPool解决方法

    本文参考:https://blog.csdn.net/sinat_26811377/article/details/99698807 出现问题 在安装第三方库的时候,Python报错pip._vend ...

  7. python psutil库安装_安装psutil模块报错安装python-devel

    psutil/_psutil_linux.c:9:20: 错误:Python.h:没有那个文件或目录 In file included from psutil/_psutil_linux.c:19: ...

  8. Sharepoint的文档库用资源管理器方式浏览报错“Explorer View ”解决方案。

    最近在安装Wss3.0时候,在拷贝已存在资料到客户电脑上时,打开客户的文档库的资源管理器的时候,提示Explorer view 错误,而且客户端访问的时候也报错. 在经过N多的查询和搜索之后终于找到的 ...

  9. oracle没报错 开不了库,oracle 数据库无法启动,报错 terminating the instance due to error 16014...

    前言: 早晨上班,开发告知数据库连接不上,说是报内存溢出,查看内存空余空间确实不足,遂将高内存进程结束,但结束后还是连接不上,重启数据库,悲剧发生了,数据库居然启不来了,因前一天改了下dastart文 ...

最新文章

  1. Koa 本地搭建 HTTPS 环境
  2. 为什么大型互联网都爱用kafka?
  3. 爬虫学习 pyspider和scrapy小结 / 与其他工具对比
  4. cocos2d-x 关于tilemap滚动时黑线闪动的问题
  5. 未启用当前数据库的 SQL Server Service Broker
  6. JVM 调优实战--jmap的使用以及内存溢出分析
  7. 计算机网络层实验路由表苏州科技,苏州科技大学计算机网络实验报告课案.docx...
  8. 03JavaScript程序设计修炼之道-2019-06-20_20-31-49
  9. python读取数据库文件的扩展名_Python读取sqlite数据库文件的方法分析
  10. 2018.09.14python学习第四天part2
  11. 计算机设计大赛国奖作品_3. 需求分析
  12. Python脚本做接口测试,抛弃接口测试工具是否可行?(一)
  13. 已知先序和中序得出后序
  14. “ISO9001:2015质量管理体系 要求”学习笔记
  15. 软件质量测试一般方法
  16. Java基础——动态数组
  17. 用于目标检测的细粒度动态头
  18. 执行多个window.onload匿名函数的方法
  19. python实战| 爬取虎牙高质量小姐姐私房照!
  20. el-input输入字母转化大写字母

热门文章

  1. 对于容斥原理反演的思考和总结
  2. C++ 面向对象(一)继承:继承、对象切割、菱形继承、虚继承、继承与组合
  3. 数据结构与算法 | 插值查找
  4. CentOS报错:“Could not resolve host: mirrorlist.centos.org; Unknown error“
  5. Linux网络IO精华指南
  6. 平均 3000-20000 块不等,有空接外包私活的入群!
  7. Kafka科普系列 | 原来Kafka中的选举有这么多?
  8. 通过 Go 语言来实现 DDD 分层设计,美滋滋!
  9. QUIC协议的演进之路
  10. Google开源框架AutoFlip 实现视频智能剪裁