動態庫和靜態庫的區別

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

動態庫和靜態庫的區別

動態庫和靜態庫的區別

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

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

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

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

本文主要通過舉例來説明在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++特性和各種類型的參數。