在平常的項目中,我們都是使用公司要求的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)
可以看到,通過這種方式,就引用了兩個庫。
通過這個簡單的例子,雖然不能說讓自己精通編譯、鏈接、靜態庫和動態庫,但是,我想,對於我的學習還是很有幫助的。最起碼,當我下次在遇到類似的問題的時候,我可以更容易的想到是因為什么原因。而這,就夠了!
