工程中編寫自己的makefile---4 庫文件

1       庫文件

編寫一個C語言程序的時候,經常會遇到好多重複或常用的部分,如果每次都重新編寫固然是可以的,不過那樣會大大降低工作效率,並且影響代碼的可讀性,更不利於後期的代碼維護。我們可以把他們製作成相應的功能函數,使用時直接調用就會很方便,還可以進行後期的功能升級。

庫本質上來說是一種可執行的二進制代碼(但不可以獨立執行),可以被操作系統載入內存執行

庫通俗的說就是把這些常用函數的目標文件打包在一起,提供相應函數的接口,便於程序員使用。庫是別人寫好的現有的,成熟的,可以複用的代碼,我們只需要知道其接口如何定義,便可以自如使用。

現實中每個程序都要依賴很多基礎的底層庫,不可能每個人的代碼都從零開始,因此庫的存在意義非同尋常。比如我們常使用的printf函數,就是c標準庫提供的函數。我們在使用時只需要包含相應的頭文件就可以使用(非靜態編譯還要有相應的庫文件)。而不用關心printf函數具體是如何實現的,這樣就大大提高了程序員編寫代碼的效率。

從使用方法上分庫大體上可以分爲兩類:靜態庫和共享庫(動態庫)。

1,在windows中靜態庫是以.lib爲後綴的文件,共享庫是以 .dll爲後綴的文件。

2,在Linux  中靜態庫是以.a爲後綴的文件,共享庫是以.so爲後綴的文件。

1.1.1        gcc關於庫的參數

1,-shared

該選項指定生成動態連接庫

2,-fPIC

表示編譯爲位置獨立(地址無關)的代碼,不用此選項的話,編譯後的代碼是位置相關的,所以動態載入時,是通過代碼拷貝的方式來滿足不同進程的需要,而不能達到真正代碼段共享的目的

3,-L

指定鏈接庫的路徑,-L. 表示要連接的庫在當前目錄中

4,-ltest

指定鏈接庫的名稱爲test,編譯器查找動態連接庫時有隱含的命名規則,即在給出的名字前面加上lib,後面加上.so來確定庫的名稱

5,-Wl,-rpath

記錄以來so文件的路徑信息

6,LD_LIBRARY_PATH

這個環境變量指示動態連接器可以裝載動態庫的路徑

注意!!!如何指定自己的動態庫路徑,假設想增加路徑/usr/local/lib

可以在/etc/profile文件中增加以下代碼來實現:

export LD_LIBRARY_PATH="/usr/local/lib:$LD_LIBRARY_PATH"

這樣,在運行程序(編譯程序是可能也可以由改變量指定,沒有嘗試過,一般是在makefile直接制定庫文件目錄)是就不會提示找不到動態鏈接庫了,此方法永久增加庫路徑

運行程序默認實在 /lib/,/usr/lib目錄找到庫文件,也可以把用戶的動態庫文件放在以上兩個目錄當中;

7,LIBRARY_PATH

靜態鏈接庫文件搜索路徑,同上

1.1.2        庫文件生成方式

以linux下的靜態庫和動態庫爲例我們看一下生成方式

1,靜態庫

a,將源文件編譯成目標文件:gcc -c a.c b.c

b,生成靜態庫:ar–rc libname.a a.o b.o

2,共享庫

a,將源文件編譯成目標文件:gcc -c a.c b.c

b,生成共享庫:gcc -fPIC -shared -o libname.so a.o b.o

由此可見靜態庫和動態庫都是對目標文件的處理,也可以說庫文件已經是機器碼文件了;

1.1.3        庫文件命名規範

在linux下,庫文件一般放在/usr/lib和/lib下,

靜態庫的名字一般爲libxxxx.a,其中xxxx是該lib的名稱

動態庫的名字一般爲libxxxx.so.major.minor,xxxx是該lib的名稱,major是主版本號, minor是副版本號

爲了在同一系統中使用不同版本的庫,可以在庫文件名後加上版本號爲後綴;

例如:libhello.so.1.0,由於程序連接默認以.so爲文件後綴名。所以爲了使用這些庫,通常使用建立符號連接的方式。

ln -s libhello.so.1.0libhello.so.1

ln -s libhello.so.1libhello.so

1.1.4        庫文件加載過程

注意!!!靜態庫和動態鏈接庫同時存在時,gcc/g++默認鏈接的是動態庫

 

1,只靜態庫的鏈接方法

gcc -o $(TARGET) -L. -llibnamemain.c -static  (默認庫在當前文件夾)

2,只共享庫的鏈接方法

gcc -o $(TARGET) -L. -llibnamemain.c          (默認庫在當前文件夾)

3, 當對動態庫與靜態庫混合連接的時候,需要使用需要作用-Wl的方式

 

3.1指定讓gcc/g++鏈接靜態庫

-Wl,-Bstatic -llibname

使用:

$(CC) -o $(TARGET)$(OBJS) -L. $(CFLAGS) -Wl,-Bstatic -llibname -Wl,-Bstatic -llibname-Wl,-Bdynamic

3.2指定讓gcc/g++鏈接動態庫

-Wl,-Bdynamic -llibname

使用:

$(CC) -o $(TARGET)$(OBJS) -L. $(CFLAGS) -Wl,-Bdynamic -llibname -Wl,-Bdynamic –llibname

注意!!!上面靜態和動態連接中代碼的區別,紅色加粗字體

系統的運行庫默認使用動態連接的方式,所以當動態庫在靜態庫前面連接時,必須在命令行最後使用動態連接的命令才能正常連接,最後的-Wl,-Bdynamic表示將缺省庫鏈接模式恢復成動態鏈接。

 

3.3指定讓gcc/g++混合鏈接動靜態庫

$(CC) -o $(TARGET)$(OBJS) -L. $(CFLAGS) -Wl,-Bstatic -llibname -Wl,-Bdynamic –llibname

 

當程序與靜態庫連接時,庫中目標文件所含的所有將被程序使用的函數的機器碼被copy到最終的可執行文件中。這就會導致最終生成的可執行代碼量相對變多,相當於編譯器將代碼補充完整了,這樣運行起來相對就快些。不過會有個缺點: 佔用磁盤和內存空間. 靜態庫會被添加到和它連接的每個程序中, 而且這些程序運行時, 都會被加載到內存中. 無形中又多消耗了更多的內存空間.

與共享庫連接的可執行文件只包含它需要的函數的引用表,而不是所有的函數代碼,只有在程序執行時,那些需要的函數代碼才被拷貝到內存中。這樣就使可執行文件比較小, 節省磁盤空間,更進一步,操作系統使用虛擬內存,使得一份共享庫駐留在內存中被多個程序使用,也同時節約了內存。不過由於運行時要去鏈接庫會花費一定的時間,執行速度相對會慢一些;

總的來說靜態庫是犧牲了空間效率,換取了時間效率,共享庫是犧牲了時間效率換取了空間效率,沒有好與壞的區別,只看具體需要了。

共享庫(動態庫)的好處是:不同的應用程序如果調用相同的庫,那麼在內存裏只需要有一份該共享庫的實例

爲了在同一系統中使用不同版本的庫,可以在庫文件名後加上版本號爲後綴,例如: libhello.so.1.0,由於程序連接默認以.so爲文件後綴名。所以爲了使用這些庫,通常使用建立符號連接的方式

另外,一個程序編好後,有時需要做一些修改和優化,如果我們要修改的剛好是庫函數的話,在接口不變的前提下,使用共享庫的程序只需要將共享庫重新編譯就可以了,而使用靜態庫的程序則需要將靜態庫重新編譯好後,將程序再重新編譯一便。

1.1.5        ldd工具

ldd可以查看可執行程序依賴那些動態庫或着動態庫依賴於那些動態庫

1,可執行程序依賴那些動態庫

[root@9527 Project]# ldd xxserver

linux-gate.so.1=>  (0x00110000)

libpthread.so.0 =>/lib/libpthread.so.0 (0x004f4000)

libc.so.6 =>/lib/libc.so.6 (0x0034c000)

/lib/ld-linux.so.2(0x00327000)

2,動態庫依賴於那些動態庫

[root@9527 objs]# ldd libadd.so

    linux-gate.so.1=>  (0x00110000)

    libc.so.6 =>/lib/libc.so.6 (0x00113000)

    /lib/ld-linux.so.2(0x00327000)

1.1.6        nm工具

使用nm工具,查看靜態庫和動態庫中有那些函數名;

1.1.7        ar工具

可以使用 ar -t libname.a 來查看一個靜態庫由那些.o文件構成

[root@9527 objs]# ar -t libadd.a

add_float.o

add_int.o

1.1.8        如何查看動態庫和靜態庫是32位,還是64位下的庫

如果是動態庫,可以使用file *.so;

如果是靜態哭,可以使用objdump -x *.a(-f可能看到更簡單的信息)

 

發佈了51 篇原創文章 · 獲贊 106 · 訪問量 33萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章