自己在linux上編譯、鏈接、動態庫和靜態庫的學習筆記


在平常的項目中,我們都是使用公司要求的makefile、makedebug一類的文件,因此,在編譯、鏈接、生成和鏈接動態庫與靜態庫的時候,我們只是簡單的使用一些已經設置的變量,只是簡單的修改、添加一些文件名,或許這次我們編譯通過了,但是,在某一個時候,可能出現了一個問題,無論簡單與否,因為平常沒有留意,導致的結果可能是花了好長時間才能解決。而如果平常只是簡單的留一下心,或許這些問題都是可以避免的。 
因此,今天我自己使用幾個文件,編譯動態庫、靜態庫以及動態庫和靜態庫的嵌套調用等問題,盡量還原我在項目中遇到的問題,盡量讓自己明白平常沒有意識到的一些東西。 
需要用到的文件列表,如下: 

/****showcoor.cpp****/
#include "showcoor.h" //顯示某一坐標 int showcoor(int x) { cout<<"coordinate:"<<x<<endl; return 0; } /****showcoor.h****/ #include <iostream> using namespace std; int showcoor(int x); /****showpoint.cpp****/ #include "showpoint.h" //顯示點坐標 int showpoint(int x,int y,int z) { showcoor(x); showcoor(y); showcoor(z); return 0; } /****showpoint.h****/ #include "showcoor.h" int showpoint(int x,int y,int z); /****main.cpp****/ #include "showpoint.h" int main() { showpoint(1,2,3); return 0; }

1、編譯生成可執行文件 
我們在單獨編譯的時候,只需要檢查頭文件,因為都放到了一個目錄,所以單獨編譯的時候,不需要依賴其他文件,就可以生成目標文件(*.o),如下: 

g++ -c showcoor.cpp g++ -c showpoint.cpp g++ -c main.cpp

編譯完成之后,就生成了對應的目標文件,如下: 

[billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call>ls *.o
main.o  showcoor.o  showpoint.o
[billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call>

編譯的時候,沒有添加依賴的頭文件,是因為默認編譯的時候,在當前目錄下去找。 
小測試1: 
如果我們把showpoint.cpp單獨移動到一個目錄下,再去編譯一下。 

[billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call>mkdir temp
[billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call>cp showpoint.cpp showpoint.h ./temp [billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call>cd ./temp [billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call>ls showpoint.cpp [billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call/temp>g++ -c showpoint.cpp In file included from showpoint.cpp:1: showpoint.h:1:22: error: showcoor.h: No such file or directory showpoint.cpp: In function 'int showpoint(int, int, int)': showpoint.cpp:4: error: 'showcoor' was not declared in this scope

通過上面可以確定的是,在編譯的時候,因為頭文件里包含了其他頭文件“showcoor.h”,而該頭文件不在當前目錄,而且不在系統指定的目錄,因此,才會提示找不到頭文件的錯誤。這個時候,我們就需要手動的指定頭文件所在的目錄,使用“-I路徑” 

[billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call/temp>g++ -I../ -c showpoint.cpp
[billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call/temp>ls showpoint.cpp showpoint.h showpoint.o

通過生成的目標文件,最后進行鏈接,就可以生成最終的可執行文件。 

[billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call>g++ -o main main.o showpoint.o showcoor.o
[billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call>./main coordinate:1 coordinate:2 coordinate:3

通過最后的鏈接,我們還可以看到函數的依賴關系。 
小測試2: 

[billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call>g++ -o main main.o showpoint.o           
showpoint.o: In function `showpoint(int, int, int)': showpoint.cpp:(.text+0x17): undefined reference to `showcoor(int)' showpoint.cpp:(.text+0x21): undefined reference to `showcoor(int)' showpoint.cpp:(.text+0x2b): undefined reference to `showcoor(int)' collect2: ld returned 1 exit status

因為showpoint.cpp會調用`showcoor(int)'函數,因此,缺少“showcoor.o”文件時,鏈接的時候,就會提示找不到該函數。 
小測試3: 

[billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call>g++ -o main main.o showcoor.o            
main.o: In function `main': main.cpp:(.text+0x14): undefined reference to `showpoint(int, int, int)' collect2: ld returned 1 exit status

因為main.cpp會首先調用`showpoint(int, int, int)'函數,因此缺少“showpoint.o”文件時,鏈接的時候,就會提示找不到“showpoint”函數。 
小測試3: 

[billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call>g++ -o main showpoint.o showcoor.o /usr/lib/gcc/x86_64-redhat-linux/4.4.6/../../../../lib64/crt1.o: In function `_start': (.text+0x20): undefined reference to `main' collect2: ld returned 1 exit status [billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call>

很明顯,因為找不到“main”函數,無法生成最終的可執行文件。 
2、生成靜態庫 
靜態函數庫: 
這類庫的名字一般是libxxx.a;利用靜態函數庫編譯成的文件比較大,因為整個函數庫的所有數據都會被整合進目標代碼中,他的優點就顯而易見了,即編譯后的執行程序不需要外部的函數庫支持,因為所有使用的函數都已經被編譯進去了。當然這也會成為他的缺點,因為如果靜態函數庫改變了,那么你的程序必須重新編譯。 
擴展閱讀: http://tech.ccidnet.com/art/2583/20080303/1378433_1.html  
生成靜態庫需要使用“ar -cr” 
c   Create the archive,也就是創建靜態庫 
r   Insert the files member... into archive (with replacement),也就是沒有生成,有的話替換。 

[billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call>g++ -c showcoor.cpp             
[billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call>ar -cr libshowcoor.a showcoor.o [billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call>ls *.a libshowcoor.a

當然,我們還可以直接通過“.cpp”文件直接生成靜態庫。如下: 

[billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call>ar -cr libshowcoor.a showcoor.cpp
[billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call>ls *.a libshowcoor.a

3、動態庫 
動態函數庫: 
這類庫的名字一般是libxxx.so;相對於靜態函數庫,動態函數庫在編譯的時候並沒有被編譯進目標代碼中,你的程序執行到相關函數時才調用該函數庫里的相應函數,因此動態函數庫所產生的可執行文件比較小。由於函數庫沒有被整合進你的程序,而是程序運行時動態的申請並調用,所以程序的運行環境中必須提供相應的庫。動態函數庫的改變並不影響你的程序,所以動態函數庫的升級比較方便。 
擴展閱讀: http://tech.ccidnet.com/art/2583/20080303/1378433_1.html  
生成動態庫的方式是:g++ -shared -fPCI -o libXXX.so *.o  
-fpic 使輸出的對象模塊是按照可重定位地址方式生成的。 
-shared指定把對應的源文件生成對應的動態鏈接庫文件。 
下面是對於-fpic的詳細解釋: 
在 Linux 下制作動態鏈接庫,“標准” 的做法是編譯成位置無關代碼(Position Independent Code,PIC),然后鏈接成一個動態鏈接庫。 
擴展閱讀: http://www.linuxidc.com/Linux/2011-06/37268.htm  

[billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call>gcc -shared -fPIC -o libshowpoint.so showpoint.cpp
[billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call>ls *.so libshowpoint.so

上述方法是通過.cpp文件直接生成動態庫。其實,也可以通過.o文件生成動態庫,如下: 

[billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call>g++ -c showpoint.cpp
[billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call>g++ -shared -fPIC -o libshowpoint.so showpoint.o /usr/bin/ld: showpoint.o: relocation R_X86_64_32 against `.bss' can not be used when making a shared object; recompile with -fPIC showpoint.o: could not read symbols: Bad value collect2: ld returned 1 exit status

上面錯誤產生的原因就是因為參數“-fPIC”的原因,因為最終生成該動態庫的時候,使用了參數“-fPIC”,表示生成的動態庫是“位置無關代碼”,而在生成“showpoint.o”的時候,是位置相關性的,所以,錯誤信息也提示了,讓使用“-fPIC”重新編譯(recompile with -fPIC)。 

[billing_dx@bmcs1]:/account/work/ymm/test/library/nest_callg++ -fPIC -c showpoint.cpp                      
[billing_dx@bmcs1]:/account/work/ymm/test/library/nest_callg++ -shared -fPIC -o libshowpoint.so showpoint.op [billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call>ls *.so libshowpoint.so

測試4: 
當然,我們通過上述方式,只是把函數“showpoint”函數封裝成了,如果我們在main函數只調用“libshowpoint.so”的話,會出現問題的,如下: 

[billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call>g++ main.cpp -L./ -lshowpoint .//libshowpoint.so: undefined reference to `showcoor(int)' collect2: ld returned 1 exit status 注:-L后面跟的是要連接的庫的路徑

-l庫名,動態庫的命名為libXXX.so的話,就可以使用-lXXX的方式引用該庫,如果是靜態庫,並且命名為libXXX.a,則同樣可以使用lXXX的方式引用該庫。 
這個時候,我們還需要使用函數“showcoor”所在的庫,因此,我們有必要把函數“showcoor”也封裝到動態庫中。 
當然,我們在最后生成可執行文件的時候,也可以鏈接同時鏈接生成的動態庫和靜態庫,如下: 

[billing_dx@bmcs1 nest_call]$ g++ -o main main.cpp -L./ -lshowpoint -lshowcoor
[billing_dx@bmcs1 nest_call]$ ./main coordinate:1 coordinate:2 coordinate:3

我們可以看到,調用動態庫和靜態庫的方式是一樣的,都是“-L路徑 -l庫名”。 
4、庫的嵌套調用 
既然我們已經把函數“showcoor”封裝成了靜態庫,因此我們完全可以通過使用靜態庫“libshowcoor.a”來生成我們最終的動態庫“libshowpoint.so”,因為靜態庫可以理解成是“目標文件(*.o)的打包”。 
測試5:動態庫調用靜態庫 
我們通過已有的靜態庫生成最終的動態庫 

[billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call>g++ -fPIC -c showpoint.cpp [billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call>gcc -shared -fPIC -o libshowpoint.so showpoint.o -L./ -lshowcoor /usr/bin/ld: .//libshowcoor.a(showcoor.o): relocation R_X86_64_32 against `.rodata' can not be used when making a shared object; recompile with -fPIC .//libshowcoor.a: could not read symbols: Bad value collect2: ld returned 1 exit status [billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call>

上述錯誤提示和原來的沒有添加“-fPIC”生成的“*.o”文件生成動態庫出現的錯誤相同,而且也給給出了解決方法,靜態庫的生成重新使用“-fPIC”進行編譯。 

[billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call>g++ -fPIC -c showcoor.cpp
[billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call>ar -cr libshowcoor.a showcoor.o [billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call>g++ -fPIC -c showpoint.cpp [billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call>g++ -shared -fPIC -o libshowpoint.so showpoint.o -L./ -lshowcoor [billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call>ls *.so libshowpoint.so

這樣最終生成的動態庫,就同時包含“showpoint”函數和“showoccr”函數,調用如下: 

[billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call>g++ main.cpp -L./ -lshowpoint
[billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call>./main coordinate:1 coordinate:2 coordinate:3

5、使用靜態庫調用的順序問題 
如果生成了多個動態庫,我們的調用順序對最終生成的文件沒有影響,測試如下: 

[billing_dx@bmcs1 nest_call]$ g++ -fPIC -c showpoint.cpp
[billing_dx@bmcs1 nest_call]$ g++ -shared -fPIC -o libshowpoint.so showpoint.o
[billing_dx@bmcs1 nest_call]$ g++ -fPIC -c showcoor.cpp
[billing_dx@bmcs1 nest_call]$ g++ -shared -fPIC -o libshowcoor.so showcoor.o
[billing_dx@bmcs1 nest_call]$ ls *.so
libshowcoor.so  libshowpoint.so
[billing_dx@bmcs1 nest_call]$ g++ -o main main.cpp -L./ -lshowcoor -lshowpoint
[billing_dx@bmcs1 nest_call]$ ./main
coordinate:1 coordinate:2 coordinate:3 [billing_dx@bmcs1 nest_call]$ g++ -o main main.cpp -L./ -lshowpoint -lshowcoor [billing_dx@bmcs1 nest_call]$ ./main coordinate:1 coordinate:2 coordinate:3

但是,如果我們使用了靜態庫,調用順序就有影響了,否則的話,編譯的時候會因為找不到函數而出錯。 
測試6: 

[billing_dx@bmcs1 nest_call]$ g++ -c showcoor.cpp
[billing_dx@bmcs1 nest_call]$ ar -cr libshowcoor.a showcoor.o  
[billing_dx@bmcs1 nest_call]$ g++ -c showpoint.cpp
[billing_dx@bmcs1 nest_call]$ ar -cr libshowpoint.a showpoint.o  
[billing_dx@bmcs1 nest_call]$ ls *.a
libshowcoor.a  libshowpoint.a
[billing_dx@bmcs1 nest_call]$ g++ -o main main.cpp -L./ -lshowpoint -lshowcoor
[billing_dx@bmcs1 nest_call]$ ./main
coordinate:1 coordinate:2 coordinate:3

調整靜態庫的調用順序: 

[billing_dx@bmcs1 nest_call]$g++ -o main main.cpp -L./ -lshowcoor -lshowpoint
.//libshowpoint.a(showpoint.o): In function `showpoint(int, int, int)': showpoint.cpp:(.text+0x17): undefined reference to `showcoor(int)' showpoint.cpp:(.text+0x21): undefined reference to `showcoor(int)' showpoint.cpp:(.text+0x2b): undefined reference to `showcoor(int)' collect2: ld returned 1 exit status

通過上面,我們可以知道,被調用的庫應該放到調用庫的后面。因為函數“showpoint”中調用了函數“showcoor”,因此,函數“showcoor”對應的庫應該放到后面才行。 
因此,如果遇到了程序需要調用A庫,而A庫調用B庫,那么我們應該寫成“g++ -o main main.cpp -L./ -libA.so -libB.so”,也就是網上所說的,調用靜態庫的時候,於是基礎的庫,越是放到最后。 
6、庫內部函數的查看 
nm用來列出目標文件的符號清單。 
擴展閱讀: http://www.linuxidc.com/Linux/2011-05/35777.htm  
nm 命令使用以下符號(用同樣的字符表示弱符號作為全局符號)之一來表示文件符號類型: 
A    Global absolute 符號。 
a    Local absolute 符號。 
B    Global bss 符號。 
b    Local bss 符號。 
D    Global data 符號。 
d    Local data 符號。 
f    源文件名稱符號。 
T    Global text 符號。 
t    Local text 符號。 
U    未定義符號。 
擴展閱讀: http://blog.chinaunix.net/uid-28458801-id-3475711.html  
上述包含“showcoor”函數和“showpoint”函數時生成的動態庫,查看內部函數: 

[billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call>nm -C libshowpoint.so |grep show 0000000000000a5f t global constructors keyed to showcoor.cpp 00000000000009ba t global constructors keyed to showpoint.cpp 00000000000009d0 T showcoor(int) 000000000000093c T showpoint(int, int, int)

如果采用只封裝“showpoint”函數的方式生成的動態庫,查看內部函數: 

[billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call>g++ -fPIC -c showpoint.cpp
[billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call>gcc -shared -fPIC -o libshowpoint.so showpoint.o [billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call>nm -C libshowpoint.so |grep show 000000000000076a t global constructors keyed to showpoint.cpp U showcoor(int) 00000000000006ec T showpoint(int, int, int)

我們可以看到,這個時候,雖然在“showpoint”函數中調用“showcoor”函數,但是該函數在庫中並未定義,所以類型是“U”。 
7、庫依賴關系的查看 
ldd命令用於判斷某個可執行的 binary 檔案含有什么動態函式庫。 
但是ldd本身不是一個程序,而僅是一個shell腳本: 
$ which ldd 
/usr/bin/ldd 
$ file /usr/bin/ldd  
/usr/bin/ldd: Bourne-Again shell script text executable 
擴展閱讀: http://blog.chinaunix.net/uid-23622436-id-3235778.html  
使用上述包含“showcoor”函數和“showpoint”函數時生成的動態庫,生成可執行文件,查看依賴庫: 

[billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call>gcc -shared -fPIC -o libshowpoint.so showpoint.cpp showcoor.cpp [billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call>g++ -o main main.cpp -L./ -lshowpoint [billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call>./main coordinate:1 coordinate:2 coordinate:3 [billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call>ldd main |grep show libshowpoint.so => /account/work/ymm/test/library/nest_call/libshowpoint.so (0x00002ba77c1ad000) [billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call>

如果兩個函數分別生成一個動態庫的話,查看依賴庫:     

[billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call>gcc -shared -fPIC -o libshowpoint.so showpoint.cpp [billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call>gcc -shared -fPIC -o libshowcoor.so showcoor.cpp [billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call>ls *.so libshowcoor.so libshowpoint.so [billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call>g++ -o main main.cpp -L./ -lshowpoint -lshowcoor [billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call>./main coordinate:1 coordinate:2 coordinate:3 [billing_dx@bmcs1]:/account/work/ymm/test/library/nest_call>ldd main|grep show libshowpoint.so => /account/work/ymm/test/library/nest_call/libshowpoint.so (0x00002abf7504a000) libshowcoor.so => /account/work/ymm/test/library/nest_call/libshowcoor.so (0x00002abf7524b000)

可以看到,通過這種方式,就引用了兩個庫。 
通過這個簡單的例子,雖然不能說讓自己精通編譯、鏈接、靜態庫和動態庫,但是,我想,對於我的學習還是很有幫助的。最起碼,當我下次在遇到類似的問題的時候,我可以更容易的想到是因為什么原因。而這,就夠了! 


免責聲明!

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



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