靜態庫在程式編譯時會被連線到目的碼中,程式執行時不再需要靜態庫;而動態庫在程式編譯時,不會放到連線的目的碼中,而是在程式執行時被載入,因此在程式執行時還需要動態庫的存在。
動態庫和靜態庫的區別
我們通常把一些公用函式製作成函式庫,供其它程式使用。
函式庫分為靜態庫和動態庫兩種。
靜態庫在程式編譯時會被連線到目的碼中,程式執行時將不再需要該靜態庫。
動態庫在程式編譯時並不會被連線到目的碼中,而是在程式執行是才被載入,因此在程式執行時還需要動態庫存在。
本文主要通過舉例來說明在Linux中如何建立靜態庫和動態庫,以及使用它們。
在建立函式庫前,我們先來準備舉例用的源程式,並將函式庫的源程式編譯成.o檔案。
第1步:編輯得到舉例的程式--hello.h、hello.c和main.c
hello.h(見程式1)為該函式庫的標頭檔案。
hello.c(見程式2)是函式庫的源程式,其中包含公用函式hello,該函式將在螢幕上輸出"Hello XXX!"。
main.c(見程式3)為測試庫檔案的主程式,在主程式中呼叫了公用函式hello。
程式1: hello.h
#ifndef HELLO_H
#define HELLO_H
void hello(const char *name)
#endif //HELLO_H
程式2: hello.c
#include
void hello(const char *name)
{
printf("Hello %s!/n", name)
}
程式3: main.c
#include "hello.h"
int main()
{
hello("everyone")
return 0
}
第2步:將hello.c編譯成.o檔案
無論靜態庫,還是動態庫,都是由.o檔案建立的。因此,我們必須將源程式hello.c通過gcc先編譯成.o檔案。
在系統提示符下鍵入以下命令得到hello.o檔案。
# gcc -c hello.c
#
(注1:本文不介紹各命令使用和其引數功能,若希望詳細瞭解它們,請參考其他文件。)
(注2:首字元"#"是系統提示符,不需要鍵入,下文相同。)
我們執行ls命令看看是否生存了hello.o檔案。
# ls
hello.c hello.h hello.o main.c
#
(注3:首字元不是"#"為系統執行結果,下文相同。)
在ls命令結果中,我們看到了hello.o檔案,本步操作完成。
下面我們先來看看如何建立靜態庫,以及使用它。
第3步:由.o檔案建立靜態庫
靜態庫檔名的命名規範是以lib為字首,緊接著跟靜態庫名,副檔名為.a。例如:我們將建立的靜態庫名為myhello,則靜態庫檔名就是libmyhello.a。在建立和使用靜態庫時,需要注意這點。建立靜態庫用ar命令。
在系統提示符下鍵入以下命令將建立靜態庫檔案libmyhello.a。
# ar cr libmyhello.a hello.o
#
我們同樣執行ls命令檢視結果:
# ls
hello.c hello.h hello.o libmyhello.a main.c
#
ls命令結果中有libmyhello.a。
第4步:在程式中使用靜態庫
靜態庫製作完了,如何使用它內部的函式呢?只需要在使用到這些公用函式的源程式中包含這些公用函式的原型宣告,然後在用gcc命令生成目標檔案時指明靜態庫名,gcc將會從靜態庫中將公用函式連線到目標檔案中。注意,gcc會在靜態庫名前加上字首lib,然後追加副檔名.a得到的靜態庫檔名來查詢靜態庫檔案。
在程式3:main.c中,我們包含了靜態庫的標頭檔案hello.h,然後在主程式main中直接呼叫公用函式hello。下面先生成目標程式hello,然後執行hello程式看看結果如何。
# gcc -o hello main.c -L. -lmyhello
# ./hello
Hello everyone!
#
我們刪除靜態庫檔案試試公用函式hello是否真的連線到目標檔案 hello中了。
# rm libmyhello.a
rm: remove regular file `libmyhello.a'? y
# ./hello
Hello everyone!
#
程式照常執行,靜態庫中的公用函式已經連線到目標檔案中了。
我們繼續看看如何在Linux中建立動態庫。我們還是從.o檔案開始。
第5步:由.o檔案建立動態庫檔案
動態庫檔名命名規範和靜態庫檔名命名規範類似,也是在動態庫名增加字首lib,但其副檔名為。例如:我們將建立的動態庫名為myhello,則動態庫檔名就是。用gcc來建立動態庫。
在系統提示符下鍵入以下命令得到動態庫檔案。
# gcc -shared -fPCI -o hello.o
#
我們照樣使用ls命令看看動態庫檔案是否生成。
# ls
hello.c hello.h hello.o main.c
#
第6步:在程式中使用動態庫
在程式中使用動態庫和使用靜態庫完全一樣,也是在使用到這些公用函式的源程式中包含這些公用函式的原型宣告,然後在用gcc命令生成目標檔案時指明動態庫名進行編譯。我們先執行gcc命令生成目標檔案,再執行它看看結果。
# gcc -o hello main.c -L. -lmyhello
# ./hello
./hello: error while loading shared libraries: : cannot open shared object file: No such file or directory
#
哦!出錯了。快看看錯誤提示,原來是找不到動態庫檔案。程式在執行時,會在/usr/lib和/lib等目錄中查詢需要的動態庫檔案。若找到,則載入動態庫,否則將提示類似上述錯誤而終止程式執行。我們將檔案 複製到目錄/usr/lib中,再試試。
# mv /usr/lib
# ./hello
Hello everyone!
#
成功了。這也進一步說明了動態庫在程式執行時是需要的。
我們回過頭看看,發現使用靜態庫和使用動態庫編譯成目標程式使用的gcc命令完全一樣,那當靜態庫和動態庫同名時,gcc命令會使用哪個庫檔案呢?抱著對問題必究到底的心情,來試試看。
先刪除 除.c和.h外的 所有檔案,恢復成我們剛剛編輯完舉例程式狀態。
# rm -f hello hello.o /usr/lib/
# ls
hello.c hello.h main.c
#
在來建立靜態庫檔案libmyhello.a和動態庫檔案。
# gcc -c hello.c
# ar cr libmyhello.a hello.o
# gcc -shared -fPCI -o hello.o
# ls
hello.c hello.h hello.o libmyhello.a main.c
#
通過上述最後一條ls命令,可以發現靜態庫檔案libmyhello.a和動態庫檔案都已經生成,並都在當前目錄中。然後,我們執行gcc命令來使用函式庫myhello生成目標檔案hello,並執行程式 hello。
# gcc -o hello main.c -L. -lmyhello
# ./hello
./hello: error while loading shared libraries: : cannot open shared object file: No such file or directory
#
從程式hello執行的結果中很容易知道,當靜態庫和動態庫同名時, gcc命令將優先使用動態庫。
iOS靜態庫與動態庫的區別
1、靜態庫:連結時會被完整的複製到可執行檔案中,被多次使用就有多份拷貝。
2、動態庫:連結時不復制,程式執行時由系統動態載入到記憶體,系統只加載一次,多個程式共用,節省記憶體。
注意修改的地方
1、mach-o type
2、enable bitcode
3、把.h檔案公佈出去
檢測記憶體洩露應用的⼀一方法:
關於c/c++靜態庫和動態庫的區別
靜態庫
之所以成為【靜態庫】,是因為在連結階段,會將彙編生成的目標檔案.o與引用到的庫一起連結打包到可執行檔案中。因此對應的連結方式稱為靜態連結。
試想一下,靜態庫與彙編生成的目標檔案一起連結為可執行檔案,那麼靜態庫必定跟.o檔案格式相似。其實一個靜態庫可以簡單看成是一組目標檔案(.o/檔案)的集合,即很多目標檔案經過壓縮打包後形成的一個檔案。靜態庫特點總結:
l 靜態庫對函式庫的連結是放在編譯時期完成的。
l 程式在執行時與函式庫再無瓜葛,移植方便。
l 浪費空間和資源,因為所有相關的目標檔案與牽涉到的函式庫被連結合成一個可執行檔案。
下面編寫一些簡單的四則運算C++類,將其編譯成靜態庫給他人用,標頭檔案如下所示:
StaticMath.h標頭檔案
#pragma once
class StaticMath
{
public:
StaticMath(void)
~StaticMath(void)
static double add(double a, double b)//加法
static double sub(double a, double b)//減法
static double mul(double a, double b)//乘法
static double div(double a, double b)//除法
void print()
}
Linux下使用ar工具、Windows下vs使用,將目標檔案壓縮到一起,並且對其進行編號和索引,以便於查詢和檢索。一般建立靜態庫的步驟如圖所示:
圖:建立靜態庫過程
Linux下建立與使用靜態庫
Linux靜態庫命名規則
Linux靜態庫命名規範,必須是"lib[your_library_name].a":lib為字首,中間是靜態庫名,副檔名為.a。
建立靜態庫(.a)
通過上面的流程可以知道,Linux建立靜態庫過程如下:
l 首先,將程式碼檔案編譯成目標檔案.o(StaticMath.o)
g++ -c
注意帶引數-c,否則直接編譯為可執行檔案
l 然後,通過ar工具將目標檔案打包成.a靜態庫檔案
ar -crv libstaticmath.a StaticMath.o
生成靜態庫libstaticmath.a。
大一點的專案會編寫makefile檔案(CMake等等工程管理工具)來生成靜態庫,輸入多個命令太麻煩了。
使用靜態庫
編寫使用上面建立的靜態庫的測試程式碼:
測試程式碼:
#include "StaticMath.h"
#include <iostream>
using namespace std
int main(int argc, char* argv[])
{
double a = 10
double b = 2
cout << "a + b = " << StaticMath::add(a,
b) << endl
cout << "a - b = " << StaticMath::sub(a,
b) << endl
cout << "a * b = " << StaticMath::mul(a,
b) << endl
cout << "a / b = " << StaticMath::div(a,
b) << endl
StaticMath sm
t()
system("pause")
return 0
}
Linux下使用靜態庫,只需要在編譯的時候,指定靜態庫的搜尋路徑(-L選項)、指定靜態庫名(不需要lib字首和.a字尾,-l選項)。
# g++ -L../StaticLibrary -lstaticmath
l -L:表示要連線的庫所在目錄
l -l:指定連結時需要的動態庫,編譯器查詢動態連線庫時有隱含的命名規則,即在給出的名字前面加上lib,後面加上.a或來確定庫的名稱。
Windows下建立與使用靜態庫
建立靜態庫()
如果是使用VS命令列生成靜態庫,也是分兩個步驟來生成程式:
l 首先,通過使用帶編譯器選項 /c 的 編譯程式碼 (cl
/c ),建立名為“”的目標檔案。
l 然後,使用庫管理器 連結程式碼 (lib ),建立靜態庫。
當然,我們一般不這麼用,使用VS工程設定更方便。建立win32控制檯程式時,勾選靜態庫型別;開啟工程“屬性面板”è”配置屬性”è”常規”,配置型別選擇靜態庫。
圖:vs靜態庫專案屬性設定
Build專案即可生成靜態庫。
使用靜態庫
測試程式碼Linux下面的一樣。有3種使用方法:
方法一:
在VS中使用靜態庫方法:
l 工程“屬性面板”è“通用屬性”è “框架和引用”è”新增引用”,將顯示“新增引用”對話方塊。 “專案”選項卡列出了當前解決方案中的各個專案以及可以引用的所有庫。 在“專案”選項卡中,選擇 StaticLibrary。 單擊“確定”。
l 新增StaticMath.h 標頭檔案目錄,必須修改包含目錄路徑。開啟工程“屬性面板”è”配置屬性”è “C/C++”è” 常規”,在“附加包含目錄”屬性值中,鍵入StaticMath.h 標頭檔案所在目錄的路徑或瀏覽至該目錄。
編譯執行OK。
圖:靜態庫測試結果(vs)
如果引用的靜態庫不是在同一解決方案下的子工程,而是使用第三方提供的靜態庫lib和標頭檔案,上面的方法設定不了。還有2中方法設定都可行。
方法二:
開啟工程“屬性面板”è”配置屬性”è “連結器”è”命令列”,輸入靜態庫的完整路徑即可。
方法三:
l “屬性面板”è”配置屬性”è “連結器”è”常規”,附加依賴庫目錄中輸入,靜態庫所在目錄;
l “屬性面板”è”配置屬性”è “連結器”è”輸入”,附加依賴庫中輸入靜態庫名。
動態庫
通過上面的介紹發現靜態庫,容易使用和理解,也達到了程式碼複用的目的,那為什麼還需要動態庫呢?
為什麼還需要動態庫?
為什麼需要動態庫,其實也是靜態庫的特點導致。
l 空間浪費是靜態庫的一個問題。
l 另一個問題是靜態庫對程式的更新、部署和釋出頁會帶來麻煩。如果靜態庫更新了,所以使用它的應用程式都需要重新編譯、釋出給使用者(對於玩家來說,可能是一個很小的改動,卻導致整個程式重新下載,全量更新)。
動態庫在程式編譯時並不會被連線到目的碼中,而是在程式執行是才被載入。不同的應用程式如果呼叫相同的庫,那麼在記憶體裡只需要有一份該共享庫的例項,規避了空間浪費問題。動態庫在程式執行是才被載入,也解決了靜態庫對程式的更新、部署和釋出頁會帶來麻煩。使用者只需要更新動態庫即可,增量更新。
動態庫特點總結:
l 動態庫把對一些庫函式的連結載入推遲到程式執行的時期。
l 可以實現程序之間的資源共享。(因此動態庫也稱為共享庫)
l 將一些程序升級變得簡單。
l 甚至可以真正做到連結載入完全由程式設計師在程式程式碼中控制(顯示呼叫)。
Window與Linux執行檔案格式不同,在建立動態庫的時候有一些差異。
l 在Windows系統下的執行檔案格式是PE格式,動態庫需要一個DllMain函式做出初始化的入口,通常在匯出函式的宣告時需要有_declspec(dllexport)關鍵字。
l Linux下gcc編譯的執行檔案預設是ELF格式,不需要初始化入口,亦不需要函式做特別的宣告,編寫比較方便。
與建立靜態庫不同的是,不需要打包工具(ar、),直接使用編譯器即可建立動態庫。
Linux下建立與使用動態庫
linux動態庫的命名規則
動態連結庫的名字形式為 ,字首是lib,字尾名為“”。
l 針對於實際庫檔案,每個共享庫都有個特殊的名字“soname”。在程式啟動後,程式通過這個名字來告訴動態載入器該載入哪個共享庫。
l 在檔案系統中,soname僅是一個連結到實際動態庫的連結。對於動態庫而言,每個庫實際上都有另一個名字給編譯器來用。它是一個指向實際庫映象檔案的連結檔案(lib+soname+)。
建立動態庫()
編寫四則運算動態庫程式碼:
DynamicMath.h標頭檔案
#pragma once
class DynamicMath
{
public:
DynamicMath(void)
~DynamicMath(void)
static double add(double a, double b)//¼Ó·¨
static double sub(double a, double b)//¼õ·¨
static double mul(double a, double b)//³Ë·¨
static double div(double a, double b)//³ý·¨
void print()
}
l 首先,生成目標檔案,此時要加編譯器選項-fpic
g++ -fPIC -c
-fPIC 建立與地址無關的編譯程式(pic,position independent code),是為了能夠在多個應用程式間共享。
l 然後,生成動態庫,此時要加連結器選項-shared
g++ -shared -o DynamicMath.o
-shared指定生成動態連結庫。
其實上面兩個步驟可以合併為一個命令:
g++ -fPIC -shared -o
使用動態庫
編寫使用動態庫的測試程式碼:
測試程式碼:
#include "../DynamicLibrary/DynamicMath.h"
#include <iostream>
using namespace std
int main(int argc, char* argv[])
{
double a = 10
double b = 2
cout << "a + b = " << DynamicMath::add(a, b) << endl
cout << "a - b = " << DynamicMath::sub(a, b) << endl
cout << "a * b = " << DynamicMath::mul(a, b) << endl
cout << "a / b = " << DynamicMath::div(a, b) << endl
DynamicMath dyn
t()
return 0
}
引用動態庫編譯成可執行檔案(跟靜態庫方式一樣):
g++ -L../DynamicLibrary -ldynmath
然後執行:./,發現竟然報錯了!!!
可能大家會猜測,是因為動態庫跟測試程式不是一個目錄,那我們驗證下是否如此:
發現還是報錯!!!那麼,在執行的時候是如何定位共享庫檔案的呢?
1) 當系統載入可執行程式碼時候,能夠知道其所依賴的庫的名字,但是還需要知道絕對路徑。此時就需要系統動態載入器(dynamic linker/loader)。
2) 對於elf格式的可執行程式,是由*來完成的,它先後搜尋elf檔案的DT_RPATH段—環境變數LD_LIBRARY_PATH—/etc/e檔案列表—/lib/,/usr/lib 目錄找到庫檔案後將其載入記憶體。
如何讓系統能夠找到它:
l 如果安裝在/lib或者/usr/lib下,那麼ld預設能夠找到,無需其他操作。
l 如果安裝在其他目錄,需要將其新增到/etc/e檔案中,步驟如下:
n 編輯/etc/檔案,加入庫檔案所在目錄的路徑
n 執行ldconfig ,該命令會重建/etc/e檔案
我們將建立的動態庫複製到/usr/lib下面,然後執行測試程式。
Windows下建立與使用動態庫
建立動態庫()
與Linux相比,在Windows系統下建立動態庫要稍微麻煩一些。首先,需要一個DllMain函式做出初始化的入口(建立win32控制檯程式時,勾選DLL型別會自動生成這個檔案):
入口檔案
// : Defines the entry point for the DLL application.
#include "stdafx.h"
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break
}
return TRUE
}
通常在匯出函式的宣告時需要有_declspec(dllexport)關鍵字:
DynamicMath.h標頭檔案
#pragma once
class DynamicMath
{
public:
__declspec(dllexport) DynamicMath(void)
__declspec(dllexport) ~DynamicMath(void)
static __declspec(dllexport) double add(double a, double b)//加法
static __declspec(dllexport) double sub(double a, double b)//減法
static __declspec(dllexport) double mul(double a, double b)//乘法
static __declspec(dllexport) double div(double a, double b)//除法
__declspec(dllexport) void print()
}
生成動態庫需要設定工程屬性,開啟工程“屬性面板”è”配置屬性”è”常規”,配置型別選擇動態庫。
圖:v動態庫專案屬性設定
Build專案即可生成動態庫。
使用動態庫
建立win32控制檯測試程式:
測試程式
#include "stdafx.h"
#include "DynamicMath.h"
#include <iostream>
using namespace std
int _tmain(int argc, _TCHAR* argv[])
{
double a = 10
double b = 2
cout << "a + b = " << DynamicMath::add(a,
b) << endl
cout << "a - b = " << DynamicMath::sub(a,
b) << endl
cout << "a * b = " << DynamicMath::mul(a,
b) << endl
cout << "a / b = " << DynamicMath::div(a,
b) << endl
DynamicMath dyn
t()
system("pause")
return 0
}
方法一:
l 工程“屬性面板”è“通用屬性”è “框架和引用”è”新增引用”,將顯示“新增引用”對話方塊。“專案”選項卡列出了當前解決方案中的各個專案以及可以引用的所有庫。 在“專案”選項卡中,選擇 DynamicLibrary。 單擊“確定”。
l 新增DynamicMath.h 標頭檔案目錄,必須修改包含目錄路徑。開啟工程“屬性面板”è”配置屬性”è “C/C++”è” 常規”,在“附加包含目錄”屬性值中,鍵入DynamicMath.h 標頭檔案所在目錄的路徑或瀏覽至該目錄。
編譯執行OK。
圖:動態庫測試結果(vs)
方法二:
l “屬性面板”è”配置屬性”è “連結器”è”常規”,附加依賴庫目錄中輸入,動態庫所在目錄;
l “屬性面板”è”配置屬性”è “連結器”è”輸入”,附加依賴庫中輸入動態庫編譯出來的。
這裡可能大家有個疑問,動態庫怎麼還有一個檔案?即無論是靜態連結庫還是動態連結庫,最後都有lib檔案,那麼兩者區別是什麼呢?其實,兩個是完全不一樣的東西。
的大小為190KB,的大小為3KB,靜態庫對應的lib檔案叫靜態庫,動態庫對應的lib檔案叫【匯入庫】。實際上靜態庫本身就包含了實際執行程式碼、符號表等等,而對於匯入庫而言,其實際的執行程式碼位於動態庫中,匯入庫只包含了地址符號表等,確保程式找到對應函式的一些基本地址資訊。
動態庫的顯式呼叫
上面介紹的動態庫使用方法和靜態庫類似屬於隱式呼叫,編譯的時候指定相應的庫和查詢路徑。其實,動態庫還可以顯式呼叫。【在C語言中】,顯示呼叫一個動態庫輕而易舉!
在Linux下顯式呼叫動態庫
#include <dlfcn.h>,提供了下面幾個介面:
l void * dlopen( const char * pathname, int mode ):函式以指定模式開啟指定的動態連線庫檔案,並返回一個控制代碼給呼叫程序。
l void* dlsym(void* handle,const char* symbol):dlsym根據動態連結庫操作控制代碼(pHandle)與符號(symbol),返回符號對應的地址。使用這個函式不但可以獲取函式地址,也可以獲取變數地址。
l int dlclose (void *handle):dlclose用於關閉指定控制代碼的動態連結庫,只有當此動態連結庫的使用計數為0時,才會真正被系統解除安裝。
l const char *dlerror(void):當動態連結庫操作函式執行失敗時,dlerror可以返回出錯資訊,返回值為NULL時表示操作函式執行成功。
在Windows下顯式呼叫動態庫
應用程式必須進行函式呼叫以在執行時顯式載入 DLL。為顯式連結到 DLL,應用程式必須:
l 呼叫 LoadLibrary(或相似的函式)以載入 DLL 和獲取模組控制代碼。
l 呼叫 GetProcAddress,以獲取指向應用程式要呼叫的每個匯出函式的函式指標。由於應用程式是通過指標呼叫 DLL 的函式,編譯器不生成外部引用,故無需與匯入庫連結。
l 使用完 DLL 後呼叫 FreeLibrary。
顯式呼叫C++動態庫注意點
對C++來說,情況稍微複雜。顯式載入一個C++動態庫的困難一部分是因為C++的name
mangling;另一部分是因為沒有提供一個合適的API來裝載類,在C++中,您可能要用到庫中的一個類,而這需要建立該類的一個例項,這不容易做到。
name mangling可以通過extern "C"解決。C++有個特定的關鍵字用來宣告採用C
binding的函式:extern "C" 。用 extern "C"宣告的函式將使用函式名作符號名,就像C函式一樣。因此,只有非成員函式才能被宣告為extern
"C",並且不能被過載。儘管限制多多,extern "C"函式還是非常有用,因為它們可以象C函式一樣被dlopen動態載入。冠以extern
"C"限定符後,並不意味著函式中無法使用C++程式碼了,相反,它仍然是一個完全的C++函式,可以使用任何C++特性和各種型別的引數。