1. 程式人生 > >Linux下編譯多個不同目錄下的檔案以及靜態庫、動態庫的使用

Linux下編譯多個不同目錄下的檔案以及靜態庫、動態庫的使用

先看兩篇博文,作為基礎知識。如果對C/C++編譯連結過程都瞭解的話,可以跳過不看。

一、  編譯不同目錄下的多個檔案

各個檔案的佈局如下:

       

head.h檔案的程式碼:

  1. #ifndef  HEAD_H
  2. #define  HEAD_H
  3. int add(int a, int b);  
  4. #endif  /*HEAD_H*/

head.cpp檔案的程式碼:

  1. #include    "head.h"
  2. int add(int a, int b)
  3. {  
  4.     return a + b;
  5. }

main.cpp檔案的程式碼(head.h標頭檔案還沒包含

  1. #include  <iostream>
  2. usingnamespace
     std;  
  3. int main(int argc, char *argv[])  
  4. {  
  5.     cout<<add(3, 5)<<endl;  
  6.     return 0;  
  7. }  

1)  以相對路徑的方式直接包含標頭檔案

為了能夠使用add函式,必須包含add所在的標頭檔案。 最簡單的方法是直接在main.cpp檔案中,用相對路徑包含head.h檔案.即 #include”function/head.h”。完整程式碼為

  1. #include  <iostream>
  2. #include    "function/head.h"
  3. usingnamespace std;  
  4. int main(
    int argc, char *argv[])  
  5. {  
  6.     cout<<add(3, 5)<<endl;  
  7.     return 0;  
  8. }  

此時,編譯命令為 :$g++ main.cpp function/head.cpp -o main

        這種用相對路徑包含標頭檔案的方式有很多弊端。當function目錄改成其它名字,或者head.h檔案放到其它目錄了,這時都要對main.cpp檔案進行修改,如果head.h標頭檔案被很多其它檔案包含的話,這個工作量就大多了。

2)  用編譯選項 –I(大寫i)

        其實,可以想一下,為什麼iostream檔案不在當前目錄下,就可以直接使用呢?這是因為,編譯器會在一些預設的目錄下(/usr/include,/usr/inlucde/c++/4.4.3等目錄)搜尋標頭檔案。所以,iostream標頭檔案不用新增。但我們不能每寫一個頭檔案就放到那裡。

        知道了原理,現在來用一下一個編譯選項 –I(include的縮寫)用來告訴編譯器,還可以去哪裡找標頭檔案。

        使用這個編譯命令,$g++ main.cpp function/head.cpp -Ifunction -o main

        此時main.cpp檔案寫成

  1. #include  <iostream>
  2. #include  <head.h>
  3. usingnamespace std;  
  4. int main(int argc, char *argv[])  
  5. {  
  6.     cout<<add(3, 5)<<endl;  
  7.     return 0;  
  8. }  

        可以看到head.h檔案是用<>而不是””。想一下C語言書中,兩者的區別。這說明,用-I選項,相當於說明了一條標準路徑。

3)  使用.o檔案

        此時,對於head.cpp檔案,在編譯命令中,還是要用到路徑function/head.cpp。現在的想法是去掉這個。這時可以先根據head.cpp檔案生成一個.o檔案,然後就可以了去掉那個路徑了。

        先cd 到function目錄。

        輸入命令:$g++ -c head.cpp -o head.o

        生成一個head.o目標檔案,

        此時把生成的head.o檔案複製到function的父目錄,就是main.cpp所在的目錄。

        然後回到function的父目錄,輸入命令$g++ main.cpp head.o -Ifunction -o main

        此時,直接使用head.o即可,無需head.cpp了。但標頭檔案head.h還是要的。因為編譯的時候要用到。連結的時候不用標頭檔案。這個可以拆分成兩條命令

        $g++ -c main.cpp -Ifunction -o main.o

        $g++ main.o head.o -o main

        第一條是編譯命令,後一條是連結命令。

二、  靜態庫

        雖然上面說到的先生成.o目標檔案,但如果function目錄下有多個.cpp檔案。那麼就要為每一個.cpp檔案都生成一個.o檔案,這個工作量是會比較大。此時可以用靜態庫。靜態庫是把多個目標檔案打包成一個檔案。Anarchive(or static library) is simply a collection of object filesstored as a single file(摘自《AdvancedLinux Programming》)。

        下面介紹靜態庫

        此時,檔案佈局為:

         

        head.h檔案程式碼

  1. #ifndef  HEAD_H
  2. #define  HEAD_H
  3. int add(int a, int b);  
  4. int sub(int a, int b);  
  5. #endif  /*HEAD_H*/

        add.cpp檔案程式碼

  1. #include    "head.h"
  2. int add(int a, int b)  
  3. {  
  4.     return a + b;  
  5. }  

        sub.cpp檔案程式碼

  1. #include    "head.h"
  2. int sub(int a, int b)  
  3. {  
  4.     return a - b;  
  5. }  


        首先,生成.o目標檔案。

        cd進入function目錄,輸入命令$g++ -c sub.cpp add.cpp  這會分別為兩個原始檔目標檔案,即生成sub.o和add.o檔案。

        然後,打包生成靜態庫,即.a檔案。

        輸入命令$ar -cr libhead.a add.o sub.o

        注意:

        1)  命令中,.a檔案要放到 .o檔案的前面

        2)  .a檔案的格式。要以lib作為字首, .a作為字尾。

        選項 c是代表create,建立.a檔案。

        r是代表replace,如果之前有建立過.a檔案,現在為了提高效能而更改了add函式裡面的程式碼,此時,就可以用r選項來代替之前.a檔案裡面的add.o

        可以用命令$ar -t libhead.a 檢視libhead.a檔案裡面包含了哪些目標檔案。其執行結果自然為add.o  sub.o

       現在回過頭來關注main.cpp檔案。

        此時的main.cpp的程式碼為.

  1. #include  <iostream>
  2. #include  <head.h>
  3. usingnamespace std;  
  4. int main(int argc, char *argv[])  
  5. {  
  6.     cout<<add(3, 5)<<endl;  
  7.     cout<<sub(10, 6)<<endl;  
  8.     return 0;  
  9. }  

        回到main.cpp檔案所在的目錄。

        輸入命令:$g++ main.cpp -Ifunction -Lfunction -lhead -o main 生成可執行程式

        現在要解釋一下使用靜態庫要用到的-L和-l(小寫的L)選項。

        -L表示要使用的靜態庫的目錄。這和前面所講的-I(大寫i,指明標頭檔案的目錄)差不多,就是用來告訴編譯器去哪裡找靜態庫。因為可能-L所指明的目錄下有很多靜態庫,所以除了要告訴去哪裡找之外,還要告訴編譯器,找哪一個靜態庫。此時,就要用到-l(小寫L)了。它用來說明連結的時候要用到哪個靜態庫。

ps:我在實驗的e伺服器上面試驗了一下,確實是可以執行的(我的所有檔案都在同一目錄下,所以引數後面跟的 ".",命令如下:


        注意:

        1. 注意是使用-lhead,而不是-llibhead

        命令中是使用-lhead,這是因為編譯器會自動在庫中新增lib字首和.a字尾。

        2. 要把-l放到命令的儘可能後的位置,必須放到原始檔的後面。

        如果使用命令中的順序,將出現下面錯誤。

        

        還記得前面所連結的兩篇博文的內容吧。當編譯器在命令中碰到main.cpp檔案後,會得到該檔案的未解決符號表。然後,會在-l所指明的靜態庫中查詢裡面的每一個目標檔案,把需要的部分抽取出來(編譯器很聰明,不會全部統統接收)。但編譯器只會對命令中的靜態庫查詢一次,之後不再查詢。如前面的錯誤命令那樣,編譯器先解析到-lhead。此時,未解決符號表都為空。編譯器不會從libhead.a庫中提取任何東西。當遇到main.cpp引數後,會得到未解決符號表。但此時已經錯過libhead.a庫了。編譯器不會再次查詢libhead.a庫了。所以就出現錯誤了。(下面的動態庫並不會出現這個問題。)

三、  動態庫

        使用命令$g++ -c -fPIC add.cpp sub.cpp生成位置無關的目標檔案。

        使用命令$g++ -shared -fPIC add.o sub.o -o libhead.so 生成.so動態連結庫。

        利用動態庫生成可執行檔案 $g++ -Ifunction -Lfunction -lhead main.cpp -o main

        嘗試執行. $./main 得到下面錯誤

        

        這說明載入的時候沒有找到libhead.so動態庫。這是因為,linux查詢動態庫的地方依次是

  1. 環境變數LD_LIBRARY_PATH指定的路徑
  2. 快取檔案/etc/ld.so.cache指定的路徑
  3. 預設的共享庫目錄,先是/lib,然後是/usr/lib

        執行./main時,明顯這三個地方都沒有找到。因為我們沒有把libhead.so檔案放到那裡。

        其實,我們可以在生成可執行檔案的時候指定要連結的動態庫是在哪個地方的。

        $g++ -Ifunction ./libhead.so main.cpp -o main

        這個命令告訴可執行檔案,在當前目錄下查詢libhead.so動態庫。注意這個命令不再使用-L 和 -l了。

        另外,還可以使用選項-Wl,-rpath,XXXX.其中XXXX表示路徑。

四、  打造自己的庫目錄和標頭檔案目錄

        三個要解決的東西:指定編譯時的標頭檔案路徑、指定連結時的動態庫路徑、指定執行時Linux載入動態庫的查詢路徑

1.指定執行時Linux載入動態庫的查詢路徑

        利用前面所說的Linux程式執行時查詢動態庫的順序,讓Linux在執行程式的時候,去自己指定的路徑搜尋動態庫。

        可以修改環境變數LD_LIBRARY_PATH或者修改/etc/ld.so.cache檔案。這裡選擇修改/etc/ld.so.cache檔案。

        1)  建立目錄/mylib/so。這個目錄可以用來存放自己以後寫的所有庫檔案。由於是在系統目錄下建立一個目錄,所以需要root許可權

        2)  建立並編輯一個mylib.conf檔案。輸入命令$sudo vim /etc/ld.so.conf.d/mylib.conf

        在mylib.conf檔案中輸入 /mylib/so 

        儲存,退出。

        3)  重建/etc/ld.so.cache檔案。輸入命令$sudo ldconfig

        輸入下面命令,生成main檔案。注意,其連結的時候是用function目錄下的libhead動態庫。

        $g++  -Ifunction -Lfunction -lhead main.cpp 

        直接執行./main。並沒有錯誤。可以執行。說明,Linux已經會在執行程式時自動在/mylib/so目錄下找動態連結庫了。

2. 指定編譯時的標頭檔案路徑

        先弄清編譯器搜尋標頭檔案的順序。

        1.先搜尋當前目錄(使用include””時)

        2.然後搜尋-I指定的目錄

        3.再搜尋環境變數CPLUS_INCLUDE_PATH、 C_INCLUDE_PATH。兩者分別是g++、gcc使用的。

        4.最後搜尋預設目錄 /usr/include  和 /usr/local/include等

        知道這些就簡單了。輸入下面命令。編輯該檔案。

        $sudo vim /etc/bash.bashrc  這個檔案是作用了所有Linux使用者的,如果不想影響其他使用者,那麼就編輯~/.bashrc檔案。

        開啟檔案後,去到檔案的最後一行。輸入下面的語句。

        

        修改環境變數。然後儲存並推出。

        此時,可以看到 已經可以不用-I選項 下面編譯命令能通過了。

        $g++ -Lfunction -lhead mian.cpp -o main

3.指定連結時的動態庫路徑

         需要注意的是,連結時使用動態庫和執行時使用動態庫是不同的。

        同樣先搞清搜尋順序:

        1. 編譯命令中-L指定的目錄

        2. 環境變數LIBRARY_PATH所指定的目錄

        3. 預設目錄。/lib、/usr/lib等。

        接下來和指定標頭檔案路徑一樣。輸入命令$sudo vim /etc/bash.bashrc   在檔案的最後一行輸入

        

        儲存,退出。

        同樣,輸入bash,使得配置資訊生效。

        這是終極目標了。其中-lhead是不能省的。因為,編譯器要知道,你要連結到哪一個動態庫。當然,如果想像C執行庫那樣,連結時預設新增的動態庫,那麼應該也是可以通過設定,把libhead.so庫作為預設庫。但並不是所有的程式都會使用這個庫。要是設定為預設新增的,反而不好。