C/C++多个链接库含有同名函数,编译会报错吗
C/C++多個鏈接庫含有同名函數,編譯會報錯嗎
- 起因
- 基本概念
- 同名函數測試
- 測試1:`.o`目標文件
- 測試2:靜態庫
- 測試3:動態庫
- 同名函數的應用
起因
由于業務需要,我司使用了Mellanox某閉源C++程序,Mellanox推薦的定制化開發方法是:對其鏈接的動態庫進行定制化開發,以添加額外的功能。
在方案討論階段,發現很多同事對動態庫/靜態庫所代表的的含義并不十分清楚,特別是當同名函數存在時,編譯、鏈接、運行的結果是什么也沒有明確的認識,故寫下這篇文章。
基本概念
程序函數庫可分為下面幾種類型:
- 動態加載函數庫(dynamically loaded libraries),在進程運行期間,使用dlfcn.h中的函數加載、調用、關閉動態庫
關于動態庫和靜態庫的優缺點,相關文章很多,這里不再贅述
同名函數測試
使用兩個.c文件test2.c和test2.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.c和test2.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.a和libtest2.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。編譯器這么做的含義是:
Stack Overflow中有個問題也談到了這點。
如果使用ld參數--whole-archive強行鏈接libtest1.a和libtest2.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.so和libtest2.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.so和libtest2.so確實都鏈接進main可執行文件中。執行結果如下:
$ LD_LIBRARY_PATH=. ./main call from test1.c可見,在動態鏈接時,不同的鏈接庫可以有同名函數,不影響編譯。這是由動態鏈接庫的性質決定的,其只有在運行時才會動態加載,并且加載的順序是由編譯時鏈接的順序決定的。這也就說符號會以第一個查找到的為準(Symbols are resolved on a first match basis)。
我們也可以通過設置LD_PRELOAD,提前將某動態庫load進內存。
同名函數的應用
有朋友會提出這樣的疑問,上面雖然做了這么多實驗,但多少有點語言律師的感覺,這些知識能改善我們日常生活嗎?日常工作中能用的到嗎?答案當然是能用得到。
最簡單的應用場景,比如某開源庫中有個函數我不喜歡,我想寫個自己的版本替換掉,那么完全可以利用上述的知識,將自己實現的某函數以動態或者靜態的方式鏈接進可執行文件中,替換自己不喜歡的版本。
工業上常見的應用有以下幾種:
總結
以上是生活随笔為你收集整理的C/C++多个链接库含有同名函数,编译会报错吗的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 从C++20 shared_ptr移除u
- 下一篇: c++的线程安全静态检查