1. 程式人生 > >條件編譯,標頭檔案,靜態庫,共享庫與多檔案程式設計

條件編譯,標頭檔案,靜態庫,共享庫與多檔案程式設計

本文轉自嵌入式Linux中文站

條件編譯

條件編譯即滿足某些條件的時候編譯某部分程式碼,常用於開發多個版本的程式,當滿足條件A時,編譯出免費版本的軟體,當滿足條件B時,編譯除vip版本的軟體,可以提高程式碼的複用率。條件編譯使用"預處理命令+巨集定義"來實現,更多巨集命令參見

$vi tutu.c
#ifdef VIP    //也可以寫成
#if defined (VIP)
//把免費版改造成VIP版的程式碼
#elif defined PRO//把免費版改造成PRO版程式碼
#endif//免費版本的程式碼

$gcc -DVIP tutu.c    //將編譯出VIP版的軟體

標頭檔案header

標頭檔案的編寫

C語言的識別符號在使用之前一定要宣告,把所有的識別符號的宣告都放在一個頭檔案中,在預處理階段把這些宣告一股腦的複製到原始檔的開頭,這樣任何識別符號在使用之前不就都被宣告過了。但這個方案引起了一個問題,就是如果我呼叫了fcn.c的函式,包括了它的標頭檔案fcn.h,我的同事也這麼做,那麼編譯的檔案中不就有了兩份fcn.h,而一個專案數百的檔案,每個檔案都有自己的標頭檔案,還會有相互呼叫的問題,這樣編譯器的壓力就會很大,所以就有了"標頭檔案衛士":

#ifndef __FCN_H
#define __FCN_H
   //fcn.c裡識別符號宣告

#endif    //__FCN_N

這三句話就保證了這個標頭檔案在整個程式中只有一份,因為一旦第一次使用這個標頭檔案的時候,__FCN_H還沒有被定義,那麼他就會被定義,裡面的宣告程式碼也會被使用,如果再由檔案使用這個標頭檔案,那麼由於__FCN_H已經被定義了,條件編譯的條件不滿足,所以裡面的宣告程式碼也就不會再被使用,反正宣告有一份就夠了。

標頭檔案的使用

標頭檔案需要使用#include巨集命令把標頭檔案原封不動的複製到當前資料夾

#include<標準標頭檔案> //在預設標頭檔案路徑裡查詢標頭檔案,如果找不到就報錯
#include"標頭檔案路徑" //按照指定的路徑查詢標頭檔案,找不到就到預設標頭檔案路徑查詢,還是找不到就報錯

靜態庫

靜態庫:由若干個.o目標檔案打包生成的.a檔案叫靜態庫檔案, 連結靜態庫就是將被呼叫的程式碼指令到呼叫模組中,並體現在最終的可執行檔案中 ,靜態庫只能靜態連結,
優勢

  • 不需跳轉,執行效率較共享庫高一些

  • 使用靜態庫的程式碼在執行時不需要依賴靜態庫

劣勢

  • 靜態庫佔用空間比較大,多次連結(多次複製)之後最終生成的可執行檔案比較大

  • 修改維護不方便,庫檔案改一點,連結它的檔案就得從頭複製一遍

生成靜態庫

  1. 編寫.c檔案 $vi add.c

  2. 生成.o檔案 $cc -c add.c

  3. 生成靜態庫檔案 $ar –r libadd.a add.o

  4. 連結靜態庫檔案 $cc -o main main.o -static -ladd -L.
    Note:

  • 對於臨時使用不在公用庫目錄的庫,連結前可以把庫的路徑新增到LIBRARY_PATH,$export LIBRARY_PATH=$LIBRARY_PATH:`pwd`

  • 或者使用直接連結$cc main.o ./libadd.a

  • 或者使用編譯選項鍊接$cc main.o -ladd –L.

  • 和動態庫不同,靜態庫不存在執行時找不到的問題,編譯時就把所有庫問題解決了。

  • add是庫名,libadd不是,是檔名

共享庫

共享庫就是由若干個目標檔案打包生成的xxx.so檔案 ,連結共享庫不是將被呼叫程式碼指令複製到呼叫模組中,而是將被呼叫程式碼指令在共享庫中的相對地址複製到呼叫模組中, 體現在最終的可執行檔案中,不論靜態連結還是動態連結(共享庫可以靜態連結也可以動態連結)。 Linux下進行連結的預設操作是先考慮動態連結庫,即如果同時存在靜態和共享庫,不特別指定的話,將與共享庫相連線

優勢

  • 共享庫佔用空間比較小, 生成的可執行檔案比較小, 即使修改了庫中的程式碼, 只要相對地址/介面保持不變, 則不需要重新連結.

劣勢

  • 使用共享庫的程式碼在執行時需要依賴共享庫, 並且執行效率較靜態庫低。

生成共享庫

  1. 編寫.c檔案 $vi add.c

  2. 生成.o檔案 $cc -c -fpic add.c

  3. 生成共享庫檔案 $cc -shared -o libadd.so add.o

  4. 連結共享庫 $cc -o main main.o -ladd -L.

Q:如果本資料夾有了libdl.a或者libdl.so會連結本資料夾的還是系統預設的???
A:如果使用$gcc main.o -L. -ldl當然會連結本資料夾的, 你當-L.是空氣啊

Note:

  • -fpic是生成位置無關碼的選項, 即生成相對地址

  • $ldd a.out #檢視a.out所依賴的共享庫資訊

  • 對於臨時使用的不在公用庫目錄的共享庫,連結後可以把庫的路徑新增到LD_LIBRARY_PATH:$export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:`pwd`

  • 如要永久更改動態庫的搜尋目錄,可以建立檔案/etc/ld.so.conf.d/my.config並將路徑寫入其中,再使用$ldconfig /etc/ld.so.conf.d/my.config配置連結選項

  • 與靜態庫不同,修改環境變數和配置檔案都是用來解決執行時找不到庫的問題,並不針對編譯時連結的問題,編譯時還是要使用-L.

靜態連結

靜態連結,即編譯時連結,使用-static可以強制連結靜態庫
Q:靜態連結時呼叫函式需要包含相應庫的標頭檔案嗎?
A:需要. 連結我們熟知的#include<stdio.h>...對應的libc.so時就是使用的靜態連結, 預設都是靜態連結共享庫,也可以使用$gcc –static強制連結靜態庫libc.a,任何一個庫都有兩個版本:.a版本和.so版本

靜態連結:

  1. 編寫.c檔案 $vi main.c

  2. 生成.o檔案 $cc -c main.c

  3. 連結庫檔案 $cc main.o –ladd

Note:

  • 開發時,使用靜態連結,以便gcc能夠找到編譯時需要的共享庫。

  • 釋出時,使用動態連結,以便程式載入執行時能夠自動找到需要的共享庫。

動態連結

動態連結:執行時連結,建立在靜態連結libdl.so(a)庫的基礎上, 程式在執行過程中動態地連結共享庫,所以需要在原始碼中寫連結程式碼,編譯器是無能為力了,只能幫到dl庫了
Q:動態連結時呼叫函式需要包含相應庫的標頭檔案嗎?
A:動態連結libadd.so是建立在靜態連結 libdl.so的基礎上的, 既然後者是靜態連結, 當然還需要包含相應的,但libadd.so裡的函式在呼叫時就不需要相應的標頭檔案了

Q:-ldl是不是因為使用的???
A:是,man dlopen,這個庫包含實現動態連結的程式碼
ATENTION:動態連結和靜態連結不是並列關係,是依存關係, 沒有靜態連結的libdl.so(a), 動態連結就是個屁

Q: 為什麼共享庫要有執行許可權, 靜態庫不需要
A:因為共享庫在執行時使用,靜態庫在編譯時裡面的程式碼已經連結到程式裡了,執行時和靜態庫就沒關係了

動態連續需要靜態連結dl.so庫:

  1. 編寫.c檔案 $vi main.c

  2. 生成.o檔案 $cc -c main.c

  3. 連結庫檔案 $cc main.o –ldl

動態連結的原始檔

#include<stdio.h>
#include<stdlib.h>
#include<dlfcn.h>

int main(){
 void *handle=dlopen("../libfcn.so",RTLD_NOW);
 if(NULL==handle)
   printf("%s",dlerror()), exit(-1);          int (*pAdd)(int,int)=(int (*)(int,int))dlsym(handle, "add");
 if(NULL==pAdd)          printf("%s",dlerror()),exit(-1);      printf("%d\n",pAdd(1,2));      int res=dlclose(handle);
 if(0!=res)
   printf("%s",dlerror()),exit(-1);      return 0; }  

Note:

  • -l是靜態連結庫名選項, dl是共享庫名(libdl.so), 用來實現我們程式中的動態載入解除安裝libadd.so