CMake和靜態庫順序
目錄
附3:gcc連結引數--whole-archive的作用 4
前言
C/C++程式的許多同學被靜態庫的依賴折騰,因為預設情況下要求被依賴的庫放在依賴它的庫後面,當一個程式或共享庫依賴的靜態庫較多時,可能會陷入解決連結問題的坑中。如果對靜態庫不熟悉,需要結構nm等工具來解決順序問題。
但也可以偷懶,不關心靜態庫的順序問題,ld為此提供了start-group和end-group兩個選項,讓包含在這兩者間的靜態庫順序可以隨意。
方法
以CMake為例,假設程式x依賴三個靜態庫:libX1.a、libX2.a和libX3.a,而libX2.a又依賴libX1.a,libX3.a依賴libX2.a和libX1.a,正常情況下的CMakeLists.txt格式如下:
add_executable( x x.cpp ) target_link_libraries( x libX1.a libX2.a libX3.a ) |
上面的寫法libX1.a、libX2.a和libX3.a的順序不能變,只能按上面的先後順序。如果去掉順序的煩惱和痛苦,可以採用如下的寫法:
target_link_libraries( x -Wl,--start-group libX1.a libX3.a libX2.a -Wl,--end-group ) 或 target_link_libraries( x -Wl,--start-group libX3.a libX2.a libX1.a -Wl,--end-group ) 都可以,完全不用關心順序。 |
前面說了start-group和end-group是ld的選項,是連結選項,不是gcc/g++的編譯選項,直接命令列或其它編譯方式也可以使用,比如命令列方式:
g++ -g -o x x.cpp -Wl,--start-group libX2.a libX1.a libX3.a -Wl,--end-group |
附1:連結靜態庫的順序問題
在連結靜態庫時,如果多個靜態庫之間存在依賴關係,則有依賴關係的靜態庫之間存在順序問題,這個在使用靜態庫時需要注意,否則會報符號找不到問題。舉例,libb.a依賴於是liba.a,而可執行檔案test只直接依賴於libb.a,則連結選項應當為“-b -a”,而不是“-a -b”,否則會報liba.a中的某些符號找不到。
gcc -c a.c ar cr liba.a a.o gcc -c b.c ar cr libb.a b.o |
雖然libb.a使用到了liba.o中的一些函式,但並不會將它們的定義包含進來,所以在連結test時需要指定這兩個庫。
另外,在編譯libb.a時是不指定liba.a的,因為編譯一個靜態庫不會使用到連結選項,而只需要指定需要依賴的標頭檔案路徑即可。
-Wl的使用:
-Wl表示後面的引數傳遞給連結器,其中l是linker的意思。 連結時指定共享庫的搜尋路徑(類似於設定LD_LIBRARY_PATH): -Wl,-rpath=/usr/local/abc:/data/abc 以上也可以分開寫: -Wl,-rpath=/usr/local/abc -Wl,-rpath=/data/abc 部分庫連結它的靜態庫,部分庫連結它的共享庫: -Wl,-static -lb -Wl,-call_shared -la -lz 指定連結器: -Wl,-dynamic-linker /lib/ld-linux.so.2 -e _so_start 指定匯出的符號: -Wl,--export-dynamic,--version-script,exports.lds exports.lds的格式可以為: { global: foo; }; 指定共享庫的soname: -Wl,--export-dynamic,--version-script,exports.lds,-soname=libqhttpd.so -rpath 增加共享庫搜尋路徑 --retain-symbols-file表示不丟棄未定義的符號和需要重定位的符號 --export-dynamic 建立一個動態連線的可執行程式時, 把所有的符號加到動態符號表中 |
附2:再議GCC編譯時的靜態庫依賴次順問題
假設有如三個原始碼檔案:
$ cat a.cpp void a() { }
$ cat b.cpp extern void a(); void b() { a(); // 呼叫a.cpp中的a() }
$ cat x.cpp extern void b(); int main() { b(); // 呼叫b.cpp中的b() return 0; } |
對應的Makefile檔案:
all: x
liba.a: a.o libb.a: b.o x: x.o liba.a libb.a # 問題出在這兒 g++ -g -o $@ $^
a.o: a.cpp g++ -g -c $^ b.o: b.cpp g++ -g -c $^ x.o: x.cpp g++ -g -c $^
clean: rm -f a.o b.o x.o x |
使用上面的Makefile編譯,將會遇到如下所示的“undefined reference”問題:
g++ -g -c x.cpp g++ -g -c a.cpp g++ -g -c b.cpp g++ -g -o x x.o liba.a libb.a # 改成“g++ -g -o x x.o libb.a liba.a”即可解決 libb.a(b.o): In function `b()': /data/jayyi/gongyi/activities/phonebook/b.cpp:2: undefined reference to `a()' collect2: ld returned 1 exit status make: *** [x] Error 1 |
這個問題的原因是b.cpp依賴a.cpp,gcc要求(實際是ld要求)libb.a須放在liba.a前面,即需要改成:g++ -g -o x x.o libb.a liba.a,也就是被依賴的庫需要放在後頭。
這是最常規的解決辦法,除此之外,只需要加入--start-group和--end-group兩個連結引數,即可保持被依賴的庫放在前頭,也就是改成如下即可:g++ -g -o $@ -Wl,--start-group $^ -Wl,--end-group。
這裡的“-Wl,”表示後面跟著的引數是傳遞給連結器ld的,gcc不關心具體是啥。“--start-group”表示範圍的開始;“--end-group”表示範圍的結束,是可選的。位於“--end-group”之後的仍然要求被依賴的庫放在後頭。
附3:gcc連結引數--whole-archive的作用
// a.h
extern void foo(); |
// a.cpp
#include <stdio.h>
void foo() { printf("foo\n"); } |
// x.cpp
#include "a.h"
int main() { foo(); return 0; } |
// Makefile
all: x
x: x.cpp liba.so g++ -g -o $@ $^
liba.so: liba.a g++ -g -fPIC -shared -o $@ $^ #g++ -g -fPIC -shared -o $@ -Wl,--whole-archive $^ -Wl,-no-whole-archive
liba.a: a.o ar cru $@ $^
a.o: a.cpp g++ -g -c $^
clean: rm -f x a.o liba.a liba.so |
$ make
g++ -g -c a.cpp ar cru liba.a a.o g++ -g -fPIC -shared -o liba.so liba.a #g++ -g -fPIC -shared -o liba.so -Wl,--whole-archive liba.a -Wl,-no-whole-archive g++ -g -o x x.cpp liba.so /tmp/cc6UYIAF.o: In function `main': /data/ld/x.cpp:5: undefined reference to `foo()' collect2: ld returned 1 exit status make: *** [x] Error 1 |
預設情況下,對於未使用到的符號(函式是一種符號),連結器不會將它們連結進共享庫和可執行程式。
這個時候,可以啟用連結引數“--whole-archive”來告訴連結器,將後面庫中所有符號都連結進來,引數“-no-whole-archive”則是重置,以避免後面庫的所有符號被連結進來。
// Makefile
all: x
x: x.cpp liba.so g++ -g -o $@ $^
liba.so: liba.a g++ -g -fPIC -shared -o $@ -Wl,--whole-archive $^ -Wl,-no-whole-archive
liba.a: a.o ar cru $@ $^
a.o: a.cpp g++ -g -c $^
clean: rm -f x a.o liba.a liba.so |
附4:如何讓有些“-l”連結靜態庫,而另一些連結共享庫?
用“-Wl,-Bstatic”指定連結靜態庫,使用“-Wl,-Bdynamic”指定連結共享庫,使用示例:
-Wl,-Bstatic -lmysqlclient_r -lssl -lcrypto -Wl,-Bdynamic -lrt -Wl,-Bdynamic -pthread -Wl,-Bstatic -lgtest |
"-Wl"表示是傳遞給連結器ld的引數,而不是編譯器gcc/g++的引數。
附5:相關博文
1) 連結靜態庫的順序問題
https://blog.csdn.net/Aquester/article/details/7780640
2) 再議GCC編譯時的靜態庫依賴順序問題
https://blog.csdn.net/Aquester/article/details/48547685
3) 如何讓有些“-l”連結靜態庫,而另一些連結共享庫?
http://blog.chinaunix.net/uid-20682147-id-5096676.html
4) 小心兩個共享庫共用同一個靜態庫
http://blog.chinaunix.net/uid-20682147-id-3760647.html
5) C/C++常見gcc編譯連結錯誤解決方法
http://blog.chinaunix.net/uid-20682147-id-5037113.html
6) CMake使用技巧集
http://control.blog.chinaunix.net/uid-20682147-id-5284633.html
7) libtool的工作原理
https://blog.csdn.net/aquester/article/details/23339825
8) 全域性變數相互依賴和初始化順序的解決辦法
https://blog.csdn.net/aquester/article/details/7780844
9) 對於glog中ShutdownGoogleLogging後不能再次InitGoogleLogging問題的解決辦法
http://blog.chinaunix.net/uid-20682147-id-3449454.html
// 如果不想依賴:
// libc.so, libgcc_s.so, librt.so, libpthread.so, libdl.so, libz.so, librt.so, libstdc++.so等,
// 連結時可指定如下選項(加到命令列最後即可,有些環境還可加上“-static-libstdc++”):
// -Wl,-Bstatic -static-libgcc -lrt -lz -pthread -ldl
// 遇到如下警告,可以忽略:
// warning: Using 'dlopen' in statically linked applications
// warning: Using 'getpwuid_r' in statically linked applications
// warning: Using 'getaddrinfo' in statically linked applications
//
// 檢查靜態連結效果:
// > ldd -r mooon_ssh
// not a dynamic executable
//
// 但如果是下列錯誤,則不能採用靜態連結(需安裝c++標準庫的靜態庫):
// cannot find -lstdc++
//
// 下列錯誤,可能是因為“/usr/bin/ld: cannot find -lstdc++”:
// the use of `mktemp' is dangerous, better use `mkstemp'
//
// libstdc++.a可能所在位置(編譯器版本要和庫版本保持相同,否則可能不相容):
// /usr/lib/gcc/i586-suse-linux/4.1.2/libstdc++.a
// /usr/lib/gcc/x86_64-redhat-linux/4.8.2/32/libstdc++.a
// /usr/lib/gcc/x86_64-redhat-linux/4.4.7/libstdc++.a
// /usr/lib/gcc/x86_64-redhat-linux/4.4.7/32/libstdc++.a
//
// libc.a可能所在位置:
// /usr/lib/libc.a
// /usr/lib64/libc.a
// /usr/lib/x86_64-redhat-linux6E/lib64/libc.a
相關文章
- 靜態順序表和動態順序表 對比
- cmake:生成靜態庫和動態庫
- DS靜態查詢之順序查詢
- 靜態連結動態連結的連結順序問題和makefile示例
- Java初始化靜態變數的時間順序Java變數
- ios靜態庫和動態庫iOS
- 動態庫和靜態庫的區別
- Windows靜態庫和動態庫的建立和使用Windows
- clion 《cmake自定義靜態庫後,生成的exe無法執行》
- iOS動態庫和靜態庫的運用iOS
- Linux下的共享庫(動態庫)和靜態庫Linux
- Linux 依賴動態庫 / 靜態庫的動態態庫 / 靜態庫Linux
- 一、靜態庫和動態庫,Makefile專案管理專案管理
- android下java的靜態庫和動態庫AndroidJava
- 在Linux中建立靜態庫和動態庫Linux
- 順序控制和狀態機之間的差別
- Java中構造方法,構造程式碼塊和靜態程式碼塊執行順序詳解Java構造方法
- Linux下的靜態庫、動態庫和動態載入庫Linux
- iOS - 靜態庫.a 和 framework 詳解iOSFramework
- 在AndroidStudio下使用cmake編譯出靜態連結庫的方法Android編譯
- 靜態庫與動態庫
- iOS中的動態庫,靜態庫和framework介紹iOSFramework
- 簡述Linux下的靜態庫和動態庫Linux
- Linux靜態庫和動態庫學習總結Linux
- Linux環境下建立靜態庫和動態庫Linux
- 列定義的順序和列儲存的順序
- 繼承與派生,多繼承,函式過載,建構函式呼叫順序 靜態多型和動態多型繼承函式多型
- 靜態庫生成
- 動靜態庫
- Android NDK祕籍--淺析靜態庫和動態庫Android
- Xcode新增Shell指令碼打包靜態庫和動態庫XCode指令碼
- 動態連結庫和靜態連結庫的區別
- cmake 連結動態連結庫
- Java中建構函式、靜態程式碼塊、程式碼塊的執行順序Java函式
- 子類繼承父類(父類和子類裡:塊、靜態塊、構造和方法覆寫)之間呼叫順序繼承
- 偽靜態、靜態和動態的區別
- Android NDK祕籍--編譯靜態庫、呼叫靜態庫Android編譯
- .net呼叫靜態庫