日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

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

發布時間:2024/2/28 c/c++ 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C/C++多个链接库含有同名函数,编译会报错吗 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

C/C++多個鏈接庫含有同名函數,編譯會報錯嗎

    • 起因
    • 基本概念
    • 同名函數測試
      • 測試1:`.o`目標文件
      • 測試2:靜態庫
      • 測試3:動態庫
    • 同名函數的應用


起因

由于業務需要,我司使用了Mellanox某閉源C++程序,Mellanox推薦的定制化開發方法是:對其鏈接的動態庫進行定制化開發,以添加額外的功能。

在方案討論階段,發現很多同事對動態庫/靜態庫所代表的的含義并不十分清楚,特別是當同名函數存在時,編譯、鏈接、運行的結果是什么也沒有明確的認識,故寫下這篇文章。

基本概念

程序函數庫可分為下面幾種類型:

  • 靜態函數庫(static libraries):在編譯期間(compile-time)靜態鏈接庫會全部拷貝進編譯對象中,一般以.a文件的存在
  • 動態函數庫(shared libraries):在程序啟動的時候加載到程序中,它可以被不同的程序共享,一般以.so文件存在
    • 動態加載函數庫(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。編譯器這么做的含義是:

  • 編譯器根據鏈接先后順序,依次查找鏈接庫。
  • 首先查找libtest1.a,發現其有main函數需要的函數void test(),因此將其進行了鏈接。
  • 再掃描到libtest2.a的時候,由于void test()已經被libtest1.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進內存。

    同名函數的應用

    有朋友會提出這樣的疑問,上面雖然做了這么多實驗,但多少有點語言律師的感覺,這些知識能改善我們日常生活嗎?日常工作中能用的到嗎?答案當然是能用得到。

    最簡單的應用場景,比如某開源庫中有個函數我不喜歡,我想寫個自己的版本替換掉,那么完全可以利用上述的知識,將自己實現的某函數以動態或者靜態的方式鏈接進可執行文件中,替換自己不喜歡的版本。

    工業上常見的應用有以下幾種:

  • 替換庫:大名鼎鼎的tcmalloc就是以這種方式運行的。我們將tcmalloc鏈接進程序,只要tcmalloc庫的查找順序優先于libc,就可以替換原生的內存管理函數為tcmalloc版本。
  • mock測試:陳碩在文章中詳述了如何在C++單元測試中mock系統調用。其中的鏈接期墊片 (link seams)方法,就是利用libc一般情況下是動態鏈接的特性,在進程中mock系統調用。
  • 總結

    以上是生活随笔為你收集整理的C/C++多个链接库含有同名函数,编译会报错吗的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。