庫的作用
大體上庫的存在,有兩方面的原因,一是代碼的複用,二是聲明和實現的分離。將功能相近的使用模組封裝成庫,使代碼的複用、管理和分發變得簡單了許多,例如著名的開源圖形庫ncurses,你可以自行編譯,更可以直接使用已經編譯好的現成的庫檔案。另外,由於庫是二進位檔案,某種意義上講,將功能的實現部分隱藏了起來,這就為商業代碼的保護提供了一種方式。
庫檔案按照連結方式和時機,可以分為動態庫和靜態庫,下面分別介紹它們在Linux環境中的建立和使用方法。 靜態連結庫
靜態庫是指在程式的連結階段,其中被用到的代碼會被直接連結到可執行檔中的庫。靜態連結的可執行程式包含了其所需的全部庫函數:所有庫函數都串連到程式中。 這類程式是完整的,其運行不需要外部庫的支援。 靜態連結程式的優點之一是其安裝之前不需要做環境準備工作 。
Linux中,靜態庫的副檔名通常為.a,它僅僅是一些目標檔案(.o)的歸檔(archive)或者說打包,另外,為了連結時能夠快速地定位其中的符號(函數、變數名等),靜態庫還會包含一個對其中符號的索引。
建立靜態庫的過程十分簡單,除編譯所必須的工具之外,要用到的命令只有兩個ar和ranlib。ar可以將各個目標檔案進行歸檔,ranlib對ar產生的歸檔檔案(即靜態庫檔案)進行索引。假設現在有這樣幾個源檔案:plus.c, sub.c及相應的標頭檔,另外還有一個用來調用庫檔案中函數的主檔案main.c,它們的內容分別是:
plus.c,
123456 |
#include "plus.h"intplus(int a, int b){ return a + b;} |
sub.c,
123456 |
#include "sub.h"intsub(int a, int b){ return a - b;} |
main.c,
1234567891011 |
#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include "plus.h"intmain(int argc, char **argv){ int a = plus(1, 2); printf("%d/n", a); return 0;} |
下面將plus.c和sub.c編譯,製作成靜態庫libmath.a,依次執行:
123 |
$ cc -c plus.c sub.c$ ar rc libmath.a plus.o sub.o$ ranlib libmath.a |
現在我們的libmath.a靜態庫已經製作完成,其中使用了ar命令的兩個選項,c表示若庫檔案不存在則建立之,r表示庫檔案中若已經存在某個目標檔案,且較舊,則執行替換。使用這個靜態庫更加簡單:
123 |
$ cc main.c -L. -lmath -o main$ ./main3 |
動態連結程式庫
動態庫和靜態庫相似,也是各個目標檔案的集合。但相比靜態連結的程式,動態連結可執行程式要小得多:這類程式運行時需要外部共用 函數庫的支援,因此好像並不完整。除了程式體小之外,動態連結允許程式包指定必須的庫,而不必將庫裝入程式包內。動態連結技術還允許多個運行中的程式共用一個庫,這樣就不會出現同一代碼的多份拷貝共占記憶體的情況了。由於這些原因,當前多數程式採用動態連結技術。
在Linux中的副檔名通常為.so。但在連結時,並不會被連結到可執行檔中,而是在執行時(需要時)由作業系統的動態載入模組動態地載入到記憶體,並連結到可執行檔地址空間的相應位置。
動態連結程式庫的建立也分為編譯和”歸檔”兩個階段,但不同的是在這兩個階段需要使用一些不同的命令選項。首先,需要將源檔案編譯成一種成為位置無關碼(PIC: Position Independent Code)的目標檔案,這種代碼可以被加入到記憶體的任何位置卻不需要載入器對其進行重定位,關於這種格式可以參考《連結器與載入器》和《程式員的自我修養–連結裝載與庫》中較為詳盡的描述。接下來需要將這些位置無關碼“歸檔”為.so檔案。整個過程只需一個工具即可,即gcc。還是上面的源檔案,執行以下命令:
12 |
$ cc -c -fpic plus.c sub.c$ cc -shared -o libmath.so *.o |
這樣,包含plus.o和sub.o的動態庫檔案libmath.so就建立完成了。其中cc(gcc的符號連結)命令的-fpic或-fPIC選項使之建立位置無關的目標檔案,使用-shared選項可以建立最終的動態庫檔案。
使用動態庫檔案有兩種方法,一種是讓作業系統的動態載入模組(如ld-linux.so.2)在執行時載入動態庫。另一種是在代碼中使用dl庫動態載入庫檔案。
先介紹下第一種方法。使用這種方法需要在編譯可執行檔時指明庫名及其路徑(對於自己的編寫的動態庫而言):
1 |
$ cc main.c -L. -lmath -o main |
這時可執行檔main就被建立了,上面的命令並沒有將libmath.so中相應的目標代碼連結到main中(你可以對比一下這裡的main和靜態連結的main的大小),只是在庫中尋找和確認main.c中用到的符號而已。但通常這個main現在還無法正常執行,這涉及到系統尋找動態庫的路徑問題,系統通常只在某些指定的目錄(標準路徑)下尋找所需的庫檔案,若在標準路徑中無法找到所需的庫,則會到環境變數LD_LIBRARY_PATH(如果存在的話)指定的目錄下尋找,若仍無法找到就會報錯。因為libmath.so在main的目前的目錄中,而目前的目錄通常並不在標準路徑之列。為了使libmath.so能夠被找到和載入,你可以把它放到標準路徑中,但更好的方法是將其所在目錄加入到LD_LIBRARY_PATH變數中。執行下面的命令:
1234 |
$ LD_LIBRARY_PATH=$LD_LIBRARY_PATH:. # 添加目前的目錄$ export LD_LIBRARY_PATH # 將環境變數匯出,使其在子shell中可用$ ./main3 |
下面介紹使用dl庫載入動態庫,dl庫中函數很少很簡練,看main.c代碼:
123456789101112131415161718192021 |
#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <dlfcn.h>#include "plus.h"intmain(int argc, char **argv){ void * lib_handle = dlopen("./libmath.so", RTLD_LAZY); if(lib_handle) { int (*plus_ptr)(int, int) = dlsym(lib_handle, "plus"); if(plus_ptr) { int a = plus_ptr(1, 2); printf("%d/n", a); } dlclose(lib_handle); } return 0;} |
main.c中,首先使用dlopen開啟需要的動態庫,其中參數RTLD_LAZY指明僅當需要調用該庫時才進行載入。dlopen返回一個控制代碼,dlsym使用該控制代碼和符號來取得相應函數的地址,這裡使用int (*)(int, int)函數指標來接收plus函數的地址。接下來使用得到的函數指標調用相應的函數,最後通過dlclose函數來關閉控制代碼。編譯這個程式需要使用dl動態連結程式庫,因此需要使用gcc的-ldl選項:
123 |
$ cc main.c -ldl -o main$ ./main3 |
.so與.a檔案的對比
最後附上libmath.a和libmath.so檔案的內容,使用objdump的-d選項查看的:
libmath.a,
In archive libmath.a:plus.o: file format elf32-i386Disassembly of section .text:00000000 <plus>: 0:55 push %ebp 1:89 e5 mov %esp,%ebp 3:8b 45 0c mov 0xc(%ebp),%eax 6:8b 55 08 mov 0x8(%ebp),%edx 9:8d 04 02 lea (%edx,%eax,1),%eax c:5d pop %ebp d:c3 ret sub.o: file format elf32-i386Disassembly of section .text:00000000 <sub>: 0:55 push %ebp 1:89 e5 mov %esp,%ebp 3:8b 45 0c mov 0xc(%ebp),%eax 6:8b 55 08 mov 0x8(%ebp),%edx 9:89 d1 mov %edx,%ecx b:29 c1 sub %eax,%ecx d:89 c8 mov %ecx,%eax f:5d pop %ebp 10:c3 ret
libmath.so:
libmath.so: file format elf32-i386Disassembly of section .init:00000324 <_init>: 324:55 push %ebp 325:89 e5 mov %esp,%ebp 327:53 push %ebx 328:83 ec 04 sub $0x4,%esp......0000044c <plus>: 44c:55 push %ebp 44d:89 e5 mov %esp,%ebp 44f:8b 45 0c mov 0xc(%ebp),%eax 452:8b 55 08 mov 0x8(%ebp),%edx 455:8d 04 02 lea (%edx,%eax,1),%eax 458:5d pop %ebp 459:c3 ret 45a:90 nop 45b:90 nop0000045c <sub>: 45c:55 push %ebp 45d:89 e5 mov %esp,%ebp 45f:8b 45 0c mov 0xc(%ebp),%eax 462:8b 55 08 mov 0x8(%ebp),%edx 465:89 d1 mov %edx,%ecx 467:29 c1 sub %eax,%ecx 469:89 c8 mov %ecx,%eax 46b:5d pop %ebp 46c:c3 ret 46d:90 nop 46e:90 nop 46f:90 nop......Disassembly of section .fini:000004a8 <_fini>: 4a8:55 push %ebp 4a9:89 e5 mov %esp,%ebp 4ab:53 push %ebx 4ac:83 ec 04 sub $0x4,%esp 4af:e8 00 00 00 00 call 4b4 <_fini+0xc> 4b4:5b pop %ebx 4b5:81 c3 40 1b 00 00 add $0x1b40,%ebx 4bb:e8 d0 fe ff ff call 390 <__do_global_dtors_aux> 4c0:59 pop %ecx 4c1:5b pop %ebx 4c2:c9 leave 4c3:c3 ret
可見,相對.a,.so有很多額外的程式碼片段,其中比較重要的是<_init>段和<_fini>段。它們分別進行一些前期和後期處理工作,例如<_init>通常在dlopen返回之前執行一些.so庫中的一些初始化工作(C++中就可能是全域構造或者靜態對象的建構函式)。
http://www.dutor.net/index.php/2010/07/dynamic-static-library/
原創部分
----------------------------------------------------------
連結 .a 靜態庫,如果是用makefile來編譯的話,調用方法:
添加AM_CFLAGS參數:AM_CFLAGS = -DTS_POINTERCAL=/"@TS_POINTERCAL@/" $(DEBUGFLAGS) -L$(top_srcdir)/src/ -ljpeg
(.a 庫存放位置為“$(top_srcdir)/src/”)
添加標頭檔:INCLUDES = -I$(top_srcdir)/src