CMake和靜態庫順序

一見發表於2018-12-07

目錄

目錄 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 $@ $^

 

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

相關文章