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

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++多个链接库含有同名函数,编译会报错吗_多个静态库中有同名函数-CSDN博客

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值