標籤:

在Linux下,如何強制讓GCC靜態鏈接?

事情的起因是這樣的:我自己筆記本裝的是Linux Mint 16,實驗室伺服器上跑的是老掉牙的CentOS 6.4,而且我沒有管理員許可權。為了提高速度,我想把筆記本上編譯好的二進位程序直接同步到伺服器上運行。但CentOS上的底層庫(glibc, libgcc, libstdc++, libboost_*)太老了,Mint上動態鏈接的程序就不能在CentOS上跑。因此我嘗試了兩種辦法:

1. 給GCC的鏈接器加上-static-libstdc++ -static-libgcc -static參數,編譯出純靜態鏈接的程序就可以在任意發行版上運行。此法對沒有使用第三方庫的代碼有效;但我寫的程序還依賴了libboost_python3等庫。實驗發現,一加上這些第三方庫,不管有沒有給鏈接器加靜態參數,生成的代碼都不是純靜態鏈接的。

2. 把程序依賴的所有動態庫文件拷到CentOS中某個目錄下,然後設置環境變數LD_LIBRARY_PATH指向這個目錄。但執行程序的時候會報出錯誤:__vdso_time: invalid mode for dlopen(): Invalid argument。

其實很多非常優秀的軟體都有portable的binary下載。如eclipse,雖然ldd發現它還是依賴了glibc等動態庫,但它就是能夠跑起來。想知道如何將程序編譯成這個樣子,使它能在任意Linux發行版上都能跑。


很簡單點事情,卻沒人直接回答,一兩句話就能說清楚:

gcc使用-Wl傳遞連接器參數,ld使用-Bdynamic強制連接動態庫,-Bstatic強制連接靜態庫。所以部分靜態,部分動態連接這麼寫:

gcc ... -Wl,-Bstatic -l& -Wl,-Bdynamic -l& ...

舉個例子,你想動態連接libA.so同時靜態連接libB.a,(先保證你的連接路徑-L裡面能找到對應的靜態或者動態庫),這麼寫:

gcc ... -Wl,-Bstatic -lA -Wl,-Bdynamic -lB ...

這裡需要注意,強制靜態或者動態連接標記之後的鏈接庫都將按照前面最近的一個標記進行鏈接,所以如果後面出現了一個libC,沒有指定連接標記,那麼libC將會被動態連接:

gcc ... -Wl,-Bstatic -lA -Wl,-Bdynamic -lB ... -lC

如果參數裡面沒指定強制的連接方式標記,那麼gcc將按照默認的優先順序去鏈接,優先動態鏈接,所以如果你這麼寫,且同時存在libC.so和libC.a那麼libC將被動態鏈接:

gcc ... -lC

由於-B連接標記會改變默認連接方式,所以在Makefile裡面如果有人這麼干:

LIBS += -Wl,-Bstatic -lC

那麼他後面的LIBS+=的庫就都只能以靜態方式連接了,有時候這是不行的,因為沒有靜態庫,所以會有人這麼應對:

LIBS += -Wl,-Bdynamic -lD

這樣就改回來了。但是這種胡亂改的行為是非常不好的,比較好的行為應該這樣:

LIBS += -l&

STATIC_LIBS += -l&

DYN_LIBS += -l&

LDFLAGS := ${LIBS} -Wl,-Bstatic ${STATIC_LIBS} -Wl,-Bdynamic ${DYN_LIBS}

這樣當你不關心怎麼連接的時候用LIBS,當你想靜態連接的時候用STATIC_LIBS,當你想動態連接的時候用DYN_LIBS。


關於這個問題,有一種簡單的方法來觀察答案:

用cmake或者qmake來生成你的makefile, 他能夠根據你的GCC版本採用合適的選項。


有些是第三方軟體在代碼裡面強制使用動態庫的,比如py就可以載入一個動態庫,這樣你是沒辦法通過gcc來搞定的,得修改源碼


這種情況不如在自己的目錄下自舉個環境。比如用 pkgsrc 或者 gentoo prefix。


有的第三方庫沒提供靜態的鏈接包,需要自己編譯處理。

LD_LIBRARY_PATH方案可行,出錯很可能是沒作對。


這個問題很經典,以前像條狗一樣的尋覓了好久,終於找到終極答案,「部分靜態鏈接部分動態鏈接」,全文太長,請參考gcc高級編譯鏈接參數


第三方庫你可以在編譯時指定庫路徑, 在gcc編譯時加上選項:

-Wl,-rpath=新庫路徑

這樣運行時就會去該路徑尋找運行時庫

那個報錯,估計你要ldd這些第三方庫看有沒依賴缺失,不過我對這也沒經驗,就是個建議。


用Go,我都這樣乾的


最高票的答案我編譯出現這個錯誤:

/usr/bin/ld: cannot find -lgcc_s
/usr/bin/ld: cannot find -lc
/usr/bin/ld: cannot find -lgcc_s
collect2: error: ld returned 1 exit status

然後搜到了gcc參數加上 `-Wl,-Bdynamic -lgcc_s `可以解決,

最後我強制鏈接靜態鏈接libfunc.a(我的目錄下同時有 libfunc.a 和 libfunc.so):

gcc main.c -Wl,-Bstatic -lfunc -L. -Wl,-Bdynamic -lgcc_s

有關 gcc_s : https://gcc.gnu.org/onlinedocs/gccint/Libgcc.html

還有一種方法是直接指定靜態鏈接的庫全名:

gcc main.c -l:libfunc.a -L //這樣也是靜態鏈接libfunc.a


我建議是用docker來封裝你的開發環境,一了百了


真是痛過才知情深!

一個static高了我很久,感謝這個好title和其他知友。

原則上純凈態編譯的程序在不同linux上也會有不同結果,我測試過,可能是底層處理依然有不同的精度差別。這個差別極其細微。


推薦閱讀:

多線程網路編程中如何合理地選擇線程數?
補碼10000000為什麼可以表示-128?
造輪子厲害是一種什麼樣的體驗?
怎樣才算有資格寫技術博客分享?
大家對 Windows Phone 系統的發展怎麼看?

TAG:編程 | Linux | CC | GCC |