Linux編譯多個不同目錄下的文件以及靜態庫、動態庫的使用
先看兩篇博文,作為基礎知識。如果對C/C++編譯鏈接過程都了解的話,可以跳過不看。
http://www.firedragonpzy.com.cn/index.php/archives/2556
http://www.cppblog.com/shifan3/archive/2007/01/05/17325.html
一、 編譯不同目錄下的多個文件
各個文件的布局如下:
head.h文件的代碼:
[cpp] view plaincopy

- #ifndef HEAD_H
- #define HEAD_H
- int add(int a, int b);
- #endif /*HEAD_H*/
head.cpp文件的代碼:
[cpp] view plaincopy

- #include "head.h"
- int add(int a, int b)
- {
- return a + b;
- }
main.cpp文件的代碼(head.h頭文件還沒包含)
[cpp] view plaincopy

- #include <iostream>
- using namespace std;
- int main(int argc, char *argv[])
- {
- cout<<add(3, 5)<<endl;
- return 0;
- }
1) 以相對路徑的方式直接包含頭文件
為了能夠使用add函數,必須包含add所在的頭文件。 最簡單的方法是直接在main.cpp文件中,用相對路徑包含head.h文件.即 #include”function/head.h”。完整代碼為
[cpp] view plaincopy

- #include <iostream>
- #include "function/head.h"
- using namespace std;
- int main(int argc, char *argv[])
- {
- cout<<add(3, 5)<<endl;
- return 0;
- }
此時,編譯命令為 :$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文件寫成
[cpp] view plaincopy

- #include <iostream>
- #include <head.h>
- using namespace std;
- int main(int argc, char *argv[])
- {
- cout<<add(3, 5)<<endl;
- return 0;
- }
可以看到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(摘自《Advanced Linux Programming》)。
下面介紹靜態庫
此時,文件布局為:
head.h文件代碼
[cpp] view plaincopy
- #ifndef HEAD_H
- #define HEAD_H
- int add(int a, int b);
- int sub(int a, int b);
- #endif /*HEAD_H*/
add.cpp文件代碼
[cpp] view plaincopy
- #include "head.h"
- int add(int a, int b)
- {
- return a + b;
- }
sub.cpp文件代碼
[cpp] view plaincopy
- #include "head.h"
- int sub(int a, int b)
- {
- return a - b;
- }
首先,生成.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的代碼為.
[cpp] view plaincopy

- #include <iostream>
- #include <head.h>
- using namespace std;
- int main(int argc, char *argv[])
- {
- cout<<add(3, 5)<<endl;
- cout<<sub(10, 6)<<endl;
- return 0;
- }
回到main.cpp文件所在的目錄。
輸入命令:$g++ main.cpp -Ifunction -Lfunction -lhead -o main 生成可執行程序
現在要解釋一下使用靜態庫要用到的-L和-l(小寫的L)選項。
-L表示要使用的靜態庫的目錄。這和前面所講的-I(大寫i)差不多,就是用來告訴編譯器去哪里找靜態庫。因為可能-L所指明的目錄下有很多靜態庫,所以除了要告訴去哪里找之外,還要告訴編譯器,找哪一個靜態庫。此時,就要用到-l(小寫L)了。它用來說明鏈接的時候要用到哪個靜態庫。
注意:
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查找動態庫的地方依次是
- 環境變量LD_LIBRARY_PATH指定的路徑
- 緩存文件/etc/ld.so.cache指定的路徑
- 默認的共享庫目錄,先是/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文件。
打開文件后,去到文件的最后一行。輸入下面的語句。
修改環境變量。然后保存并推出。
輸入命令$bash 或者直接打開一個新的命令行窗口,使得配置信息生效。原理可以參考:http://blog.csdn.net/luotuo44/article/details/8917764
此時,可以看到 已經可以不用-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庫作為默認庫。但并不是所有的程序都會使用這個庫。要是設置為默認添加的,反而不好。
RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成