動態庫與靜態庫


1.在windows中靜態庫是以 .lib 爲後綴的文件,共享庫是以 .dll 爲後綴的文件。在linux中靜態庫是以 .a 爲後綴的文件,共享庫是以 .so爲後綴的文件。
2.以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到最終的可執行文件中。這就會導致最終生成的可執行代碼量相對變多,相當於編譯器將代碼補充完整了,這樣運行起來相對就快些。不過會有個缺點: 佔用磁盤和內存空間. 靜態庫會被添加到和它連接的每個程序中, 而且這些程序運行時, 都會被加載到內存中. 無形中又多消耗了更多的內存空間.

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

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

3.、庫的類型

(一) 在windows中

.dll 動態庫

.lib 靜態庫

庫即爲源代碼的二進制文件

(二) 在linux中

.so 動態庫

.a      靜態庫

 

(三) 靜態庫和動態庫的優缺點

我們通常把一些公用函數製作成函數庫,供其它程序使用。

函數庫分爲靜態庫和動態庫兩種。

靜態庫在程序編譯時會被連接到目標代碼中,程序運行時將不再需要該靜態庫。

動態庫在程序編譯時並不會被連接到目標代碼中,而是在程序運行是才被載入,因此在程序運行時還需要動態庫存在

 

1.什麼是庫

在windows平臺和linux平臺下都大量存在着庫。

本質上來說庫是一種可執行代碼的二進制形式,可以被操作系統載入內存執行。

由於windows和linux的本質不同,因此二者庫的二進制是不兼容的。

本文僅限於介紹linux下的庫

 

2.庫的種類

linux下的庫有兩種:靜態庫和共享庫(動態庫)。

 二者的不同點在於代碼被載入的時刻不同。

靜態庫的代碼在編譯過程中已經被載入可執行程序,因此體積較大。

共享庫的代碼是在可執行程序運行時才載入內存的,在編譯過程中僅簡單的引用,因此代碼體積較小。

 

3.庫存在的意義

庫是別人寫好的現有的,成熟的,可以複用的代碼,你可以使用但要記得遵守許可協議。

現實中每個程序都要依賴很多基礎的底層庫,不可能每個人的代碼都從零開始,因此庫的存在意義非同尋常。共享庫的好處是,不同的應用程序如果調用相同的庫,那麼在內存裏只需要有一份該共享庫的實例。

 

4.庫文件是如何產生的在linux下

靜態庫的後綴是.a,它的產生分兩步

Step 1.由源文件編譯生成一堆.o,每個.o裏都包含這個編譯單元的符號表

Step 2.ar命令將很多.o轉換成.a,成文靜態庫

動態庫的後綴是.so,它由gcc加特定參數編譯產生。

例如:

$ gcc -fPIC -c *.c $ gcc -shared -Wl,-soname, libfoo.so.1 -olibfoo.so.1.0 *.

 

5.庫文件是如何命名的,有沒有什麼規範

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

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

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

 

6.如何知道一個可執行程序依賴哪些庫

ldd命令可以查看一個可執行程序依賴的共享庫,

例如# ldd /bin/lnlibc.so.6

=> /lib/libc.so.6 (0×40021000)/lib/ld-linux.so.2

=> /lib/ld- linux.so.2 (0×40000000)

可以看到ln命令依賴於libc庫和ld-linux庫

 

7.可執行程序在執行的時候如何定位共享庫文件

當系統加載可執行代碼時候,能夠知道其所依賴的庫的名字,但是還需要知道絕對路徑

此時就需要系統動態載入器(dynamiclinker/loader)

對於elf格式的可執行程序,是由ld-linux.so*來完成的

它先後搜索elf文件的 DT_RPATH段—環境變量LD_LIBRARY_PATH—/etc/ld.so.cache文件列表—/lib/,/usr/lib目錄

找到庫文件後將其載入內存

 

8.在新安裝一個庫之後如何讓系統能夠找到他

如果安裝在/lib或者/usr/lib下,那麼ld默認能夠找到,無需其他操作。

如果安裝在其他目錄,需要將其添加到/etc/ld.so.cache文件中,步驟如下

1.編輯/etc/ld.so.conf文件,加入庫文件所在目錄的路徑

2.運行ldconfig,該命令會重建/etc/ld.so.cache文件

 

############################################################

 

 linux中編譯靜態庫(.a)和動態庫(.so)的基本方法

 

(四) 靜態庫

 

在linux環境中, 使用ar命令創建靜態庫文件.如下是命令的選項:

          d -----從指定的靜態庫文件中刪除文件

          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中的函數了.

 

 

(五) 動態庫

 

1.創建共享庫

     gcc -shared -o libapue.soerror.o errorlog.o

這樣就創建了共享庫!

2.編譯共享庫

    假設共享庫位於當前目錄(即跟程序文件相同的目錄中)

gcc -o test -L. -lapue test.c

這樣就編譯出了不包含函數代碼可執行文件了,但是但你運行時會發現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 的 庫文件.

              連接器 在 標準搜索目錄 中 尋找 這個 庫文件, 庫文件 的 真正 名 字

4.

1. 靜態庫lib和動態dll的區別

1.1 項目類型

VS在建Win32項目時,有以下選項:

  • windows應用程序
  • 控制檯應用程序
  • DLL
  • 靜態庫

最後兩個類型:DLL和靜態庫,這兩種項目類型是不可以單獨運行的,必須在Windows應用程序調用他們執行,是提供的庫函數而已。

1.2 兩種lib的區別:

(1)靜態庫(.lib)

函數和數據被編譯進一個二進制文件(通常擴展名爲.LIB)。在使用靜態庫的情況下,在編譯鏈接可執行文件時,鏈接器從庫中複製這些函數和數據並把它們和應用程序的其他模塊組合起來創建最終的可執行文件(.EXE文件)。當發佈產品時,只需要發佈這個可執行文件,並不需要發佈被使用的靜態庫。

(2)動態庫(.lib文件和.dll文件)

在使用動態庫的時候,編譯後往往提供兩個文件:一個引入庫(.lib)文件(也稱“導入庫文件”)和一個DLL(.dll)文件。當然到了後面會告訴你如果只提供一個DLL文件,使用顯示連接的方式也可以調用,只是稍加麻煩而已。

雖然引入庫的後綴名也是“lib”,但是,動態庫的引入庫文件和靜態庫文件有着本質的區別。對一個DLL文件來說,其引入庫文件(.lib)包含該DLL導出的函數和變量的符號名,而.dll文件包含該DLL實際的函數和數據。在使用動態庫的情況下,在編譯鏈接可執行文件時,只需要鏈接該DLL的引入庫文件,該DLL中的函數代碼和數據並不可複製到可執行文件,直到可執行程序運行時,纔去加載所需的DLL,將該DLL映射到進程的地址空間中,然後訪問DLL中導出的函數。這時,在發佈產品時,除了發佈可執行文件以外,同時還需要發佈該程序將要調用的動態鏈接庫。

只有當EXE程序確實要調用這些DLL模塊的情況下,系統纔會將它們裝載到內存空間中。這種方式不僅減少了EXE文件的大小和對內存空間的需求,而且使這些DLL模塊可以同時被多個應用程序使用。如果DLL不在內存中,系統就將其加載到內存中。當鏈接Windows程序以產生一個可執行文件時,你必須鏈接由編程環境提供的專門的 “引入庫(import library)”。這些引入庫包含了動態鏈接庫名稱和所有Windows函數調用的引用信息。鏈接程序使用該信息在.EXE文件中構造一個表,當加載程序時,Windows使用它將調用轉換爲Windows函數。

引入庫LIb和靜態庫Lib的區別:

引入庫和靜態庫的區別很大,他們實質是不一樣的東西。靜態庫本身就包含了實際執行代碼、符號表等等,而對於引入庫而言,其實際的執行代碼位於動態庫中,引入庫只包含了地址符號表等,確保程序找到對應函數的一些基本地址信息。但是引入庫文件的引入方式和靜態庫一樣,要在鏈接路徑上添加找到這些.lib的路徑。

1.3 動態庫(Lib和DLL)和靜態庫Lib的區別: 
其實上面已經提到了區別,這裏再總結一下: 
(1)靜態鏈接庫與動態鏈接庫都是共享代碼的方式,如果採用靜態鏈接庫,lib 中的指令都全部被直接包含在最終生成的 EXE 文件中了,最終的可執行文件exe會比較大。但是若使用 DLL,該 DLL 不必被包含在最終 EXE 文件中,EXE 文件執行時可以“動態”地引用和卸載這個與 EXE 獨立的 DLL 文件。 
(2)靜態鏈接庫和動態鏈接庫的另外一個區別在於靜態鏈接庫中不能再包含其他的動態鏈接庫或者靜態庫,而在動態鏈接庫中還可以再包含其他的動態或靜態鏈接 庫。靜態鏈接庫與靜態鏈接庫調用規則總體比較如下。

靜態鏈接庫運行之前就加載了,而且一直存在直到關閉程序,動態DLL實在運行時再加載,不用一直佔內存,dll模塊內部更改了,是要替換Dll即可,方便維護。優點明顯,但是dll如果丟失或被誤刪,就無法運行了


2. 靜態庫lib和動態dll的使用

2. 1 動態dll的使用

動態鏈接庫的使用需要庫的開發者提供生成的.lib文件和.dll文件。或者只提供dll文件。使用時只能使用dll中導出的函數,未導出的函數只能在dll內部使用。Dll的調用有顯示連接和隱式連接兩種:隱式連接需要三個東西,分別是*.h頭文件,lib庫(動態的),DLL庫;顯示連接只需要.dll文件即可。

2.1.1 隱式連接 
隱式鏈接需要三個東西,分別是*.h頭文件,lib庫(動態的),DLL庫,而這裏的lib庫僅是編譯的時候用,運行時候不用,運行時只用Dll

2.1.1.1 添加Lib

方法1: 通過設置工程配置來添加lib庫.

A、添加工程的頭文件目錄:工程->屬性->配置屬性->c/c++->常規->附加包含目錄:加上頭文件存放目錄。添加頭文件參考2.2.1.2 
B、添加文件引用的lib靜態庫路徑:工程->屬性->配置屬性->鏈接器->常規->附加庫目錄:加上lib文件存放目錄。 
C 然後添加工程引用的lib文件名:工程->屬性->配置屬性->鏈接器->輸入->附加依賴項:加上lib文件名。

這種方法比較繁瑣,且不直觀,而且還可能要爭對debug版本和release版本作不同的配置,因爲我們生成的兩個版本的庫可能放在不同的目錄中的.

方法2: 使用編譯語句:

#ifdef _DEBUG
#pragma comment(lib,"..\\debug\\LedCtrlBoard.lib")
#else
#pragma comment(lib,"..\\release\\LedCtrlBoard.lib")
#endif
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

這種方法直觀,方便,且可以按如上直接區分出Debug版本和Release版本的不同目錄.當然,通過宏,還可以區分更多版本.但是在指定目錄時,不小心容易出錯.

方法3: 直接添加庫文件到工程中.

就像你添加.h和.cpp文件一樣,把lib文件添加到工程文件列表中去.

VC中,切換到”解決方案視圖”—>選中要添加lib的工程–>點擊右鍵–>”添加”–>”現有項”–>選擇lib文件–>確定.

這個方法適用於在我的工程的debug版本和Release版本中都使用同一個lib庫文件時.這樣就省去了你1方法配置環境的繁瑣,也省去了方法2種語句的可能性錯誤發生.。

2.1.1.2添加頭文件

在調用DLL中導出的函數之前要include對應的頭文件,可以寫絕對路徑,也可以拷貝到工程調用源文件的同一目錄下,也可以通過VS添加(include)頭文件目錄,VS中配置方法: 
(1)VS項目->右擊屬性->配置屬性->VC++目錄->包含目錄 
(2)VS項目->右擊屬性->配置屬性->C/C++->常規->附加包含目錄

2.1.1.3 添加dll

一般將dll拷貝到運行時目錄即可,與調用者exe文件在同一目錄,當然有其他方法添加環境變量PATH, 
(1)VS項目->右擊屬性->配置屬性->VC++目錄->可執行目錄 
(2)設定DLL目錄的位置,具體方法爲:項目右擊->屬性 -> 配置屬性 -> 調試 ->工作目錄,在這裏設置dll的路徑就可以了

注1:release版本和debug版本的區分,每種版本的設置都是獨立的,要分別設置 
注2:單純添加lib目錄的方法有以下幾種方法

(1):把.lib文件放在當前使用它的工程目錄下;(如:.exe所在目錄,或者工程代碼所在的目錄) 
(2):對某一個項目:項目”->“屬性”->“配置屬性”->“VC++目錄”->“常規”->“附加庫目錄” 
(3):在vs中,“項目”->“屬性”->“配置屬性”->“鏈接器”->“常規”->“附加庫目錄” 
(4):放在開發環境IDE的lib庫目錄下,例如:“C:\Program Files\Microsoft Visual Studio 8\VC\lib”,這是vs2005的vc開發的lib庫目錄。

注:在VS屬性中配置路徑時可以用絕對路徑,也可以用相對路徑,其中./表示當前目錄,而../表示上一級目錄

上面僅僅對單個項目有效,我們配置過opencv庫的都知道,有一種是全局配置lib和include頭文件,對所有的項目有效,以Debug版爲例,步驟如下: 
(1) 點擊“視圖”→“其他窗口”→“屬性管理器” 
(2) 從左側項目中打開“Debug| Win32”→“Microsoft.Cpp.Win32.user” 
(3) 雙擊“Microsoft.Cpp.Win32.user”,在彈出的窗口,點擊左側VC++目錄,編輯右側的可執行文件目錄、包含目錄與庫目錄,分別添加對應的路徑 
(4) 附加依賴項,單擊“鏈接器”→“輸入”→“附加依賴項”,填入依賴項.lib後綴的文件名。

2.1.2 顯示連接

隱式鏈接雖然實現較簡單,但除了必須的.dll文件外還需要DLL的.h文件和.lib文件,在那些只提供.dll文件的場合就無法使用,而只能採用顯式鏈接的方式。這種方式通過調用API函數來完成對DLL的加載與卸載,能更加有效地使用內存,在編寫大型應用程序時往往採用此方式。這種方法編程具體實現步驟如下:

①使用Windows API函數Load Library或者MFC提供的AfxLoadLibrary將DLL模塊映像到進程的內存空間,對DLL模塊進行動態加載。

②使用GetProcAddress函數得到要調用DLL中的函數的指針。

③不用DLL時,用Free Library函數或者AfxFreeLibrary函數從進程的地址空間顯式卸載DLL。

使用LoadLibrary顯式鏈接,那麼在函數的參數中可以指定DLL文件的完整路徑;如果不指定路徑,或者進行隱式鏈接,Windows將遵循下面的搜索順序來定位搜索DLL:

  • 包含EXE文件的目錄
  • 工程目錄
  • Windows系統目錄
  • Windows目錄
  • 列在Path環境變量中的一系列目錄

2.2 靜態庫lib的使用

靜態lib中,一個lib文件實際上是任意個obj文件的集合,obj文件是cpp文件編譯生成的。靜態庫的.lib文件包含了鏈接庫的所有信息(函數代碼和接口信息)。所以我們在調用靜態庫.lib時,只需要包含頭文件目錄(../include. .h),以及附加庫目錄即可。因此,靜態鏈接庫的使用需要庫的開發者提供生成庫的.h頭文件和.lib文件

在VC中新建一個static library類型的工程TestLib,加入test.cpp文件和test.h文件(頭文件內包括函數聲明),然後編譯,就生成了TestLib.lib文件。

別的工程要使用這個lib方式:

(1)添加lib 
方法1):直接用”項目右擊”->”添加”–>”現有項”–>選擇lib文件–>確定,通過這種方式將.lib加入工程 
方法2):工程屬性-> 配置屬性->鏈接器->輸入->附加依賴項中添加要使用的Lib庫的名字;在工程屬性-> 配置屬性->鏈接器->輸入->附加庫目錄中輸入.lib文件所在路徑(相對或絕對路徑)

方法3):或者在源代碼中加入指令#pragma comment(lib, “TestLib.lib”),也可以指定完整路徑(絕對路徑或相對路徑)#pragma comment(lib, “..\Debug\TestLib.lib”)。可以通過宏#if defined(_DEBUG)來區分用release或debug版本的lib。另外這裏如果不指定完整路徑,也要像方法2一樣添加附加庫目錄。

如果不在工程屬性中添加附加lib庫目錄,也可以將靜態裏邊庫比如TestLib.lib拷貝到工程所在目錄,或者拷貝到執行文件生成的目錄,或者拷貝到系統Lib目錄中。

(2). 添加頭文件

加入相應的頭文件test.h。#include “test.h”

include file path可以爲絕對路徑,也可以爲相對於工程所在目錄的相對路徑,如果頭文件比較多,可以在project>settings>c/c++>preprocessor的Additional include directories中填入你的頭文件所在目錄,編譯時會自動在此目錄查找頭文件。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章