CMake和靜態庫順序
目錄
目錄 1
前言 1
方法 1
附1:連結靜態庫的順序問題 2
附2:再議GCC編譯時的靜態庫依賴次順問題 3
附3:gcc連結引數--whole-archive的作用 4
附4:讓有些“-l”連結靜態庫,而另一些連結共享庫? 6
附5:相關博文 6
前言
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 [email protected] $^
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 [email protected] -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 [email protected] $^
liba.so: liba.a g++ -g -fPIC -shared -o [email protected] $^ #g++ -g -fPIC -shared -o [email protected] -Wl,--whole-archive $^ -Wl,-no-whole-archive
liba.a: a.o ar cru [email protected] $^
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 [email protected] $^
liba.so: liba.a g++ -g -fPIC -shared -o [email protected] -Wl,--whole-archive $^ -Wl,-no-whole-archive
liba.a: a.o ar cru [email protected] $^
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