CMake和靜態庫順序


目錄

目錄 1

前言 1

方法 1

1:鏈接靜態庫的順序問題 2

2:再議GCC編譯時的靜態庫依賴次順問題 3

3gcc鏈接參數--whole-archive的作用 4

4:讓有些“-l”鏈接靜態庫,而另一些鏈接共享庫? 6

5:相關博文 6

 

前言

C/C++程序的許多同學被靜態庫的依賴折騰,因為默認情況下要求被依賴的庫放在依賴它的庫后面,當一個程序或共享庫依賴的靜態庫較多時,可能會陷入解決鏈接問題的坑中。如果對靜態庫不熟悉,需要結構nm等工具來解決順序問題。

但也可以偷懶,不關心靜態庫的順序問題,ld為此提供了start-groupend-group兩個選項,讓包含在這兩者間的靜態庫順序可以隨意。

方法

CMake為例,假設程序x依賴三個靜態庫:libX1.alibX2.alibX3.a,而libX2.a又依賴libX1.alibX3.a依賴libX2.alibX1.a,正常情況下的CMakeLists.txt格式如下:

add_executable(

    x

    x.cpp

)

target_link_libraries(

    x

    libX1.a

    libX2.a

    libX3.a

)

 

上面的寫法libX1.alibX2.alibX3.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-groupend-groupld的選項,是鏈接選項,不是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.cppgcc要求(實際是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”之后的仍然要求被依賴的庫放在后頭。

附3gcc鏈接參數--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) 對於glogShutdownGoogleLogging后不能再次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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM