例如我要在一段代碼中多次交換兩個變量的值,我可以在代碼中多次寫入
i=x;
x=y;
y=i;
不過這樣未免有點麻煩我們可以編寫一個change_two_int()函數進行簡化。
定義如下函數:
void change_two_int(int *a,int *b)
{
int c;
c=*a;
*a=*b;
*b=c;
}
這樣每次要進行交換時只需調用 change_two_int(&x , &y);即可,是否方便了許多?
那麼我們要討論的和這些有什麼關系呢?庫通俗的說就是把這些常用函數的目標文件打包在一起,提供相應函數的接口,便於程序員使用。庫是別人寫好的現有的,成熟的,可以復用的代碼,我們只需要知道其接口如何定義,便可以自如使用。
現實中每個程序都要依賴很多基礎的底層庫,不可能每個人的代碼都從零開始,因此庫的存在意義非同尋常。比如我們常使用的printf函數,就是c標准庫提供的函數。我們在使用時只需要包含相應的頭文件就可以使用(非靜態編譯還要有相應的庫文件)。而不用關心printf函數具體是如何實現的,這樣就大大提高了程序員編寫代碼的效率。從使用方法上分庫大體上可以分為兩類:靜態庫和共享庫。在windows中靜態庫是以 .lib 為後綴的文件,共享庫是以 .dll 為後綴的文件。在linux中靜態庫是以 .a 為後綴的文件,共享庫是以 .so為後綴的文件。
以linux下的靜態庫和動態庫為例我們研究一下,首先我們看一下他們的生成方式
靜態庫:
首先將源文件編譯成目標文件:gcc –c a.c b.c
生成靜態庫:ar –rc libstatic.a a.o b.o
共享庫:
同靜態庫一樣編譯成目標文件:gcc –c a.c b.c
生成共享庫:gcc –fPIC –shared –o libshared.so a.o b.o
由此可見靜態庫和動態庫都是對目標文件的處理,也可以說庫文件已經是機器碼文件了,靜態庫和共享庫的加載過程有很大的區別。
靜態庫的鏈接方法:
gcc –o staticcode –L. –lstatic main.c –static(默認庫在當前文件夾)
共享庫的鏈接方法:
gcc –o sharedcode -L. –lshared main.c(默認庫在當前文件夾)
當程序與靜態庫連接時,庫中目標文件所含的所有將被程序使用的函數的機器碼被copy到最終的可執行文件中。這就會導致最終生成的可執行代碼量相對變多,相當於編譯器將代碼補充完整了,這樣運行起來相對就快些。不過會有個缺點: 占用磁盤和內存空間. 靜態庫會被添加到和它連接的每個程序中, 而且這些程序運行時, 都會被加載到內存中. 無形中又多消耗了更多的內存空間.
與共享庫連接的可執行文件只包含它需要的函數的引用表,而不是所有的函數代碼,只有在程序執行時, 那些需要的函數代碼才被拷貝到內存中。這樣就使可執行文件比較小, 節省磁盤空間,更進一步,操作系統使用虛擬內存,使得一份共享庫駐留在內存中被多個程序使用,也同時節約了內存。不過由於運行時要去鏈接庫會花費一定的時間,執行速度相對會慢一些,總的來說靜態庫是犧牲了空間效率,換取了時間效率,共享庫是犧牲了時間效率換取了空間效率,沒有好與壞的區別,只看具體需要了。
另外,.一個程序編好後,有時需要做一些修改和優化,如果我們要修改的剛好是庫函數的話,在接口不變的前提下,使用共享庫的程序只需要將共享庫重新編譯就可以了,而使用靜態庫的程序則需要將靜態庫重新編譯好後,將程序再重新編譯一便。
總結:一、庫的類型
庫即為源代碼的二進制文件
靜態庫在程序編譯時會被連接到目標代碼中,程序運行時將不再需要該靜態庫。
動態庫在程序編譯時並不會被連接到目標代碼中,而是在程序運行是才被載入,因此在程序運行時還需要動態庫存在
由於windows和linux的本質不同,因此二者庫的二進制是不兼容的。
本文僅限於介紹linux下的庫
靜態庫的代碼在編譯過程中已經被載入可執行程序,因此體積較大。
共享庫的代碼是在可執行程序運行時才載入內存的,在編譯過程中僅簡單的引用,因此代碼體積較小。
Step 2.ar命令將很多.o轉換成.a,成文靜態庫
動態庫的後綴是.so,它由gcc加特定參數編譯產生。
例如:
$ gcc -fPIC -c *.c $ gcc -shared -Wl,-soname, libfoo.so.1 -olibfoo.so.1.0 *.
動態庫的名字一般為libxxxx.so.major.minor,xxxx是該lib的名稱,major是主版本號, minor是副版本號
=> /lib/libc.so.6 (0×40021000)/lib/ld-linux.so.2
=> /lib/ld- linux.so.2 (0×40000000)
可以看到ln命令依賴於libc庫和ld-linux庫
對於elf格式的可執行程序,是由ld-linux.so*來完成的
它先後搜索elf文件的 DT_RPATH段—環境變量LD_LIBRARY_PATH—/etc/ld.so.cache文件列表—/lib/,/usr/lib目錄
找到庫文件後將其載入內存
1.編輯/etc/ld.so.conf文件,加入庫文件所在目錄的路徑
2.運行ldconfig,該命令會重建/etc/ld.so.cache文件
############################################################
linux中編譯靜態庫(.a)和動態庫(.so)的基本方法
m -----把文件移動到指定的靜態庫文件中
p -----把靜態庫文件中指定的文件輸出到標准輸出
q -----快速地把文件追加到靜態庫文件中
r -----把文件插入到靜態庫文件中
t -----顯示靜態庫文件中文件的列表
x -----從靜態庫文件中提取文件
還有多個修飾符修改以上基本選項,詳細請man ar 以下列出三個:
a -----把新的目標文件(*.o)添加到靜態庫文件中現有文件之後
b-----***************************************之前
v -----使用詳細模式
ar 命令的命令行格式如下:
ar[-]{dmpqrtx}[abcfilNoPsSuvV] [membername] [count] archive files...
參數archive定義庫的名稱, files是庫文件中包含的目標文件的清單, 用空格分隔每個文件.
比如創建一個靜態庫文件的命令如下:
ar r libapue.a error.oerrorlog.o lockreg.o
這樣就了libapue.a靜態庫文件, 可以用 t 選項顯示包含在庫中的文件
創建庫文件之後,可以創建這個靜態庫文件的索引來幫助提高和庫連接的其他程序的編譯速度:
使用ranlib程序創建庫的索引,索引存放在庫文件內部.
ranlib libapue.a
用nm程序顯示存檔文件的索引,它可以顯示目標文件的符號
nm libapue.a | more
如果是顯示目標文件的符號:
nm error.o | more
如何使用呢?如下所示:
gcc -o test test.c libapue.a
這樣就可以在test.c中調用在libapue.a中的函數了.
這樣就編譯出了不包含函數代碼可執行文件了,但是但你運行時會發現linux動態加載器找不到libapue.so文件.
可以用ldd 命令查看可執行文件依賴什麼共享庫:
ldd test
如何才能讓動態加載器發現庫文件呢?有兩種方法可以解決:
1.環境變量
exportLD_LIBRARY_PATH="$LD_LIBRARY_PATH:."
2.修改/etc/ld.so.conf文件.
一般應用程序的庫文件不與系統庫文件放在同一個目錄下,一般把應用程序的共享庫文件放在/usr/local/lib下,新建一個屬於自己的目錄apue,然後把剛才libapue.so復制過去就行了
同時在/etc/ld.so.conf中新增一行:
/usr/local/lib/apue
以後在編譯程序時加上編譯選項:
-L /usr/local/lib/apue -lapue
/*
參數的配置通過mangcc可以看到
-llibrary
連接名為 library 的 庫文件.
連接器 在 標准搜索目錄 中 尋找 這個 庫文件, 庫文件 的 真正 名 字
Linux開發工具(gcc,gdb,make,shell)——三級優化分別做了什麼
Linux開發工具(gcc gdb make shell)——GDB中list命令詳解
Linux開發工具(gcc gdb make shell)——GCC中
Linux開發工具(gcc,gdb,make,shell)——linux中靜態庫和動態庫的區別(三)(2)
Linux開發工具(gcc,gdb,make,shell)——linux中靜態庫和動態庫的區別(三)
Linux開發工具(gcc,gdb,make,shell)——linux中靜態庫和動態庫的區別(三)(1)