linux連結庫簡介
最近要考linux程式設計,發現linux庫這個東西,怎麼也無法了解,在網上兜了一大圈,看了很多東西,才算模模糊糊搞懂了,下面把自己總結的東西貼上來與大家共享:
在linux系統中,可将多個目标檔案打包成庫檔案,以便在程式設計時随時調用,而不必重新編寫或定義,這種包稱為庫函數。庫檔案是一些預先編譯好的函數的集合,那些函數都是按照可再使用的原則編寫的。它們通常由一組互相關聯的用來完成某項常見工作的函數構成。比如用來處理螢幕顯示情況的函數(curses庫)等。
1.基礎知識:
linux下GCC在編譯程式時要經曆預處理,編譯,彙編和連接配接四個階段。
預處理階段,主要處理#include和#define,它把#include包含進來的.h 檔案插入到#include所在的位置,生成.i檔案;
編譯階段,是最重要的階段,在這個階段GCC首先檢查文法然後把檔案轉換成彙程式設計式,生成.s檔案;上面這兩步的輸出檔案都是文本檔案,我們可以用諸如cat的文本處理等指令閱讀這些輸出檔案;
彙編階段,把*.s檔案翻譯成二進制機器指令檔案*.o,需要反彙編工具如GDB的幫助才能讀懂它;
連接配接階段,gcc在這個階段把所有的*.o檔案連接配接成一個可執行檔案,庫檔案的連接配接也在這步完成。
GCC編譯過程:
預處理-----> 編譯 ----> 彙編 ----> 連結
1)預處理(Pre-processing)
在該階段,編譯器将C源代碼中的包含的頭檔案如stdio.h編譯進來,使用者可以使用gcc的選項”-E”進行檢視。
用法:#gcc -E hello.c -o hello.i
作用:将hello.c預處理輸出hello.i檔案。
2)編譯階段(Compiling)
在這個階段中,Gcc首先要檢查代碼的規範性、是否有文法錯誤等,以确定代碼的實際要做的工作,在檢查無誤後,Gcc把代碼翻譯成彙編語言。使用者可以使用”-S”選項來進行檢視,該選項隻進行編譯而不進行彙編,生成彙編代碼。
用法:[root]# gcc -S hello.i -o hello.s
作用:将預處理輸出檔案hello.i彙編成hello.s檔案。
3)彙編階段(Assembling)
彙編階段是把編譯階段生成的”.s”檔案轉成二進制目标代碼.
用法:[root]# gcc -c hello.s -o hello.o
作用:将彙編輸出檔案test.s編譯輸出test.o檔案。
4)連結階段(Link)
在成功編譯之後,就進入了連結階段。
無選項連結
用法:[root]# gcc hello.o -o hello
作用:将編譯輸出檔案hello.o連結成最終可執行檔案hello。
2. 标準庫與非标準庫
庫函數可分為标準與非标準(自定義)庫兩大類。
(1)标準庫檔案是公用的,系統中的任何使用者都可以利用這些庫函數,一般儲存在/lib或者/usr/lib目錄裡,并以頭檔案的方式提供包含調用。編譯時要告訴C語言編譯器(更确切地說是連結程式)應去查找哪些庫檔案,預設情況下,它隻會查找标準庫檔案。
标準庫在使用時,gcc等編譯程式能夠自動連接配接,是以在隻需要包含其定義的頭檔案即可,如libc.a。libc.a為标準C函數庫,它包含了諸如記憶體管理或者輸入輸出操作的基本函數。
(2)非标準庫在連接配接時,必須加上-lname(name為去掉lib和尾部的.a或.so後的庫名)參數;非标準庫可以放在任意目錄中,一般放于目前目錄中,但當放置在非系統預設搜尋路徑中時,需要用-Ldir(dir為路徑名稱)指定搜尋路徑。
例如,數學函數并不是C标準庫的組成部分,他們是由數學庫/usr/lib/libm.a所定義的,因而在使用該庫中的數學函數時,除了用#include<math.h>将頭檔案/usr/include/math.h加入到程式檔案中,還要明确的用gcc的-lm選項來連接配接這個庫(數學庫libm.a放置在系統預設的庫搜尋路徑/usr/lib中,因而不需要-Ldir參數)。
3. 靜态庫與共享庫
函數庫一般分為靜态和共享(也稱動态庫)兩種格式,庫檔案必須遵守一定的命名規則,庫檔案的名字永遠以lib打頭,随後是說明函數庫情況的部分(比如用c表示這是一個 C語言庫,而m表示這是一個數學運算庫等)。檔案名的最後部分以一個句點(.)開始,然後給出這個庫檔案的類型,.a為靜态庫檔案,.so和.sa為共享型庫檔案。無論動态庫還是靜态庫都需要用.o檔案來生成。
一般 Linux 系統把 /lib 和 /usr/lib 兩個目錄作為預設的庫(動态庫或靜态庫)搜尋路徑,是以使用這兩個目錄中的庫時不需要進行設定搜尋路徑即可直接使用。對于處于預設庫搜尋路徑之外的庫,需要将庫的位置添加到庫的搜尋路徑之中(如修改環境變量),也可以在程式連接配接時,通過-L參數來指定。
但由于在運作時,程式還需要連接配接動态庫,因而,對于動态庫,最好還是将庫的位置添加到庫的搜尋路徑之中或是把所用的庫拷貝到系統預設的庫搜尋目錄中。靜态庫則無此限制。
(1)靜态庫
$ gcc -c hello.c -o hello.o
$ ar cqs libhello.a hello.o
連結,這裡指定了靜态庫的位置 //-static參數
$ gcc -static -libhello test.c -o test
它等價于:
$ gcc test.o libhello.a -o test
看引用庫情況。
$ ldd test
靜态庫也叫做檔案(archive),檔案名按慣例都以.a結尾,比如C語言标準庫/usr/lib/libc.a,X11庫/usr/X11R6/lib/libX11.a等。它所包含的成員是若幹.o檔案,靜态庫中的各個成員(.o檔案)沒有特殊的存在格式,僅僅是一個.o檔案的集合。在連接配接時,靜态庫的檔案代碼會被拷貝到可執行檔案中,是以靜态連接配接的可執行檔案一般比較大一些。
在Linux(Unix)中使用工具“ar”對靜态庫進行維護管理,建立靜态庫用ar指令,在系統提示符下鍵入以下指令将建立靜态庫檔案libmyhello.a。
# ar -rc libmyhello.a hello.o
靜态庫的缺點是,如果我們在同一時間運作多個程式而它們又都使用着來自同一個函數庫裡的函數時,記憶體裡就會有許多份同一函數的備份,在程式檔案本身也有許多份同樣的備份。這會消耗大量寶貴的記憶體和硬碟空間。
(2)共享庫
$ gcc -shared -Wall -fPIC hello.o -o libhello.so
-shared:該選項指定生成動态連接配接庫(讓連接配接器生成T類型的導出符号表,有時候也生成弱連接配接W類型的導出符号),不用該标志外部程式無法連接配接。相當于一個可執行檔案。
-fPIC:表示編譯為位置獨立的代碼,不用此選項的話編譯後的代碼是位置相關的,是以動态載入時是通過代碼拷貝的方式來滿足不同程序的需要,而不能達到真正代碼段共享的目的。
-fPIC : If supported for the target machine, emit position-independent code, suitable for dynamic linking and avoiding any limit on the size of the global offset table. This option makes a difference on the m68k, PowerPC and SPARC. Position-independent code requires special support, and therefore works only on certain machines.
也稱動态庫,許多UNIX系統支援共享庫,它同時克服了靜态庫在記憶體和硬碟方面的無謂消耗。連結時,動态庫的代碼不會被加入可執行檔案中,而是在程式被執行的時候加載。
共享庫的預設存放位置和靜态庫是一樣的,但有着不同的檔案字尾。在一個典型的 Linux系統上,C語言标準庫的共享版本是/usr/lib/libc.so N,其中的N是主版本号。當靜态庫和動态庫同名時,gcc指令将優先使用動态庫。
可用gcc來建立動态庫,在系統提示符下鍵入以下指令得到動态庫檔案libmyhello.so。
# gcc -shared -fPCI -o libmyhello.so hello.o
4. 對比
靜态庫在程式編譯時會被連接配接到目标代碼中,程式運作時将不再需要該靜态庫。動态庫在程式編譯時并不會被連接配接到目标代碼中,而是在程式運作時才被載入,是以在程式運作時還需要動态庫存在。
假如程式是在編譯時加載庫文檔的,就是使用了靜态庫。假如是在運作時加載目标代碼,就成為動态庫。換句話說,假如是使用靜态庫,則靜态庫代碼在編譯時就拷貝到了程式的代碼段,程式的體積會膨脹。假如使用動态庫,則程式中隻保留庫文檔的名字和函數名,在運作時去查找庫文檔和函數體,程式的體積基本變化不大。
靜态庫的原則是“以空間換時間”,增加程式體積,減少運作時間;動态庫則是“以時間換空間”,增加了運作時間,但減少了程式本身的體積。
5. 總結
标準靜态庫或共享庫,路徑和庫名都不需顯示指定;
非标準靜态庫,必須顯示指定庫名,不在預設路徑中時需指定路徑;
非标準共享庫,編譯時:必須指定庫名,不在預設路徑中需指定路徑名,
運作時,不在預設路徑中時需要将庫位置添加到庫搜尋路徑中或拷貝庫到預設路徑中。
連結方法I
拷貝到系統庫裡再連結,讓gcc自己查找
$ sudo cp libhello.so /usr/lib
gcc test.o -lhello -o test
這裡我們可以看到了 -lhello 選項,-l[lib_name] 指定庫名,他會主動搜尋lib[lib_name].so 。
這個搜尋的路徑可以通過 gcc --print-search-dirs來查找。
連結方法II
手動指定庫路徑
$ cc -o test test.o -lhello -B .
這裡的-B 選項就添加目前目錄到gcc搜尋的路徑之中。這樣連結在執行時候仍舊需要指定庫路徑(連結和執行是分開的)。
也可以直接用-L指定動态連結庫所在的目錄
$ cc -o test test.o -lhello -L .
需要添加系統變量 LD_LIBRARY_PATH :
$ export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
/etc/ld.so.conf 是系統對動态連結庫進行查找的路徑配置檔案,是系統連結工具/usr/bin/ld查找動态連結庫的地圖。
是以,也可以:
pwd >> /etc/ld.so.conf
ldconfig
這個時候再來檢測一下test程式的庫連結狀況
$ ldd test
比靜态連結的程式多了一個 libhello.so,這是靜态與動态的最大差別,靜态情況下,他把庫直接加載到程式裡,而在動态連結的時候,他隻是保留接口,将動态庫與程式代碼獨立。這樣就可以提高代碼的可複用度,和降低程式的耦合度。
Linux作業系統上面的動态共享庫大緻分為三類:
1、作業系統級别的共享庫和基礎的系統工具庫
比方說libc.so, libz.so, libpthread.so等等,這些系統庫會被放在/lib和/usr/lib目錄下面,如果是64位作業系統,還會有/lib64和/usr /lib64目錄。如果作業系統帶有圖形界面,那麼還會有/usr/X11R6/lib目錄,如果是64位作業系統,還有/usr/X11R6 /lib64目錄。此外還可能有其他特定Linux版本的系統庫目錄。這些系統庫檔案的完整和版本的正确,確定了Linux上面各種程式能夠正常的運作。
2、應用程式級别的系統共享庫
并非作業系統自帶,但是可能被很多應用程式所共享的庫,一般會被放在/usr/local/lib和/usr/local/lib64這兩個目錄下面。很多你自行編譯安裝的程式都會在編譯的時候自動把/usr/local/lib加入gcc的-L參數,而在運作的時候自動到/usr/local /lib下面去尋找共享庫。
以上兩類的動态共享庫,應用程式會自動尋找到他們,以上這些目錄預設就被加入到動态連結程式的搜尋路徑裡。Linux的系統共享庫搜尋路徑定義在/etc/ld.so.conf這個配置檔案裡面。這個檔案的内容格式大緻如下:
/usr/X11R6/lib64
/usr/X11R6/lib
/usr/local/lib
/lib64
/lib
/usr/lib64
/usr/lib
/usr/local/lib64
/usr/local/ImageMagick/lib
假設我們自己編譯安裝的ImageMagick圖形庫在/usr/local/ImageMagick目錄下面,并且希望其他應用程式都可以使用 ImageMagick的動态共享庫,那麼我們隻需要把/usr/local/ImageMagick/lib目錄加入/etc/ld.so.conf檔案裡面,然後執行:ldconfig 指令即可。
ldcofig将搜尋以上所有的目錄,為共享庫建立一個緩存檔案/etc/ld.so.cache。為了确認ldconfig已經搜尋到ImageMagick的庫,我們可以用上面介紹的strings指令從ld.so.cache裡面抽取文本資訊來檢查一下:
strings /etc/ld.so.cache | grep ImageMagick
3、應用程式獨享的動态共享庫
有很多共享庫隻被特定的應用程式使用,那麼就沒有必要加入系統庫路徑,以免應用程式的共享庫之間發生版本沖突。是以Linux還可以通過設定環境變量LD_LIBRARY_PATH來臨時指定應用程式的共享庫搜尋路徑,就像我們上面舉的那個例子一樣,我們可以在應用程式的啟動腳本裡面預先設定 LD_LIBRARY_PATH,指定本應用程式附加的共享庫搜尋路徑,進而讓應用程式找到它。
具體如下:
(1)建立自己的頭檔案路徑/home/user/workspace/include和庫檔案路徑/home/user/workspace/lib,這兩個目錄用來存放我自己編寫的頭檔案和庫檔案;
(2)接下來寫個頭檔案和庫檔案作測試,在/home/user/workspace/include下建立檔案test.h,在/home/user/workspace/lib下建立test.c,test.h中是一些函數原型,test.c是函數的實作;
(3)通過指令gcc -fPIC -shared -o libtest.so test.c在/home/user/workspace/lib生成了一個動态連結庫檔案libtest.so;
(4)現在把庫檔案路徑添加進.bash_profile檔案,添加内容如下:
# my code
C_INCLUDE_PATH=/home/cheney/workspace/include
export C_INCLUDE_PATH
LD_LIBRARY_PATH=/home/cheney/workspace/lib
export LD_LIBRARY_PATH
然後通過source .bash_profile把.bash_profile檔案即時更新了