動態連結庫和靜態連結庫的區別

lostinai發表於2014-03-10

靜態連線庫就是把(lib)檔案中用到的函式程式碼直接連結進目標程式,程式執行的時候不再需要其它的庫檔案;動態連結就是把呼叫的函式所在檔案模組(DLL)和呼叫函式在檔案中的位置等資訊連結進目標程式,程式執行的時候再從DLL中尋找相應函式程式碼,因此需要相應DLL檔案的支援。  

靜態連結庫與動態連結庫都是共享程式碼的方式,如果採用靜態連結庫,則無論你願不願意,lib 中的指令都全部被直接包含在最終生成的 EXE 檔案中了。但是若使用 DLL,該 DLL 不必被包含在最終 EXE 檔案中,EXE 檔案執行時可以“動態”地引用和解除安裝這個與 EXE 獨立的 DLL 檔案。靜態連結庫和動態連結庫的另外一個區別在於靜態連結庫中不能再包含其他的動態連結庫或者靜態庫,而在動態連結庫中還可以再包含其他的動態或靜態連結庫。


“每一個lib檔案就是若干函式(假設只有函式)的定義”
lib庫有兩種,一種是包含了函式所在DLL檔案和檔案中函式位置的資訊,稱為匯出庫;一種是包含函式程式碼本身,一般現有的DLL,用的是前一種庫;以前在DOS下的TC/BC等,是後一種庫。包含函式原型宣告的,是標頭檔案(.h)。  

  lib有靜態lib和動態lib之分。

  靜態lib將匯出宣告和實現都放在lib中。編譯後所有程式碼都嵌入到宿主程式

  動態lib相當於一個h檔案,是對實現部分(.dll檔案)的匯出部分的宣告。編譯後只是將匯出宣告部分編譯到宿主程式中,執行時候需要相應的dll檔案支援


  
“通過#include包含這些函式宣告的標頭檔案後,我們的應用程式就可以使用lib檔案中的函式”

還要指定編譯器連結相應的庫檔案。在IDE環境下,一般是一次指定所有用到的庫檔案,編譯器自己尋找每個模組需要的庫;在命令列編譯環境下,需要指定每個模組呼叫的庫。

“那他和直接給出那個函式定義的檔案,比如.cpp檔案,和標頭檔案有什麼區別,靜態連結庫有什麼用”
cpp檔案是原始碼,庫檔案是編譯後的二進位制程式碼,比如你可以呼叫Windows的API,但是不能看到其原始碼一樣。

“還有不明白的是,靜態連結庫中的lib檔案只要用到,則整個lib檔案的內容都放進了exe檔案中,那它是被編譯進去還是連結的時候連線進去的呢?”
是在連結的時候將lib連結到目的碼中。

 

靜態連結庫(Lib)
在VC++6.0中new一個名稱為libTest的static library工程,

並新建lib.h和lib.cpp兩個檔案,lib.h和lib.cpp的原始碼如下:

//檔案:lib.h
#ifndef LIB_H
#define LIB_H
extern "C" int add(int x,int y);   //宣告為C編譯、連線方式的外部函式
#endif

//檔案:lib.cpp
#include "lib.h"
int add(int x,int y)
{
return x + y;
}


  編譯這個工程就得到了一個.lib檔案,這個檔案就是一個函式庫,它提供了add的功能。將標頭檔案和.lib檔案提交給使用者後,使用者就可以直接使用其中的add函式了。

  標準Turbo C2.0中的C庫函式(我們用來的scanf、printf、memcpy、strcpy等)就來自這種靜態庫。

下面來看看怎麼使用這個庫,在libTest工程所在的工作區內new一個libCall工程。libCall工程僅包含一個main.cpp檔案,它演示了靜態連結庫的呼叫方法,其原始碼如下:

 

 

#include <stdio.h>
#include "../lib.h"//不可丟失
#pragma comment( lib, "..//debug//libTest.lib" )  //指定與靜態庫一起連線
int main(int argc, char* argv[])
{
printf( "2 + 3 = %d", add( 2, 3 ) );
}


  靜態連結庫的呼叫就是這麼簡單,或許我們每天都在用,可是我們沒有明白這個概念。程式碼中#pragma comment( lib , "..//debug//libTest.lib" )的意思是指本檔案生成的.obj檔案應與libTest.lib一起連線


一、引言

通常情況下,對函式庫的連結是放在編譯時期(compile time)完成的。所有相關的物件檔案(object file)與牽涉到的函式庫(library)被連結合成一個可執行檔案(executable file)。程式執行時,與函式庫再無瓜葛,因為所有需要的函式已拷貝到自己門下。所以這些函式庫被成為靜態庫(static libaray),通常檔案名為“libxxx.a”的形式。

其實,我們也可以把對一些庫函式的連結載入推遲到程式執行的時期(runtime)。這就是如雷貫耳的動態連結庫(dynamic link library)技術。

二、動態連結庫的特點與優勢

首先讓我們來看一下,把庫函式推遲到程式執行時期載入的好處:

1. 可以實現程式之間的資源共享。

什麼概念呢?就是說,某個程式的在執行中要呼叫某個動態連結庫函式的時候,操作系統首先會檢視所有正在執行的程式,看在記憶體裡是否已有此庫函式的拷貝了。如果有,則讓其共享那一個拷貝;只有沒有才連結載入。這樣的模式雖然會帶來一些“動態連結”額外的開銷,卻大大的節省了系統記憶體資源。C的標準庫就是動態連結庫,也就是說系統中所有執行的程式共享著同一個C標準庫的程式碼段。

2. 將一些程式升級變得簡單。使用者只需要升級動態連結庫,而無需重新編譯連結其他原有的程式碼就可以完成整個程式的升級。Windows 就是一個很好的例子。

3. 甚至可以真正坐到連結載入完全由程式設計師在程式程式碼中控制。

程式設計師在編寫程式的時候,可以明確的指明什麼時候或者什麼情況下,連結載入哪個動態連結庫函式。你可以有一個相當大的軟體,但每次執行的時候,由於不同的操作需求,只有一小部分程式被載入記憶體。所有的函式本著“有需求才調入”的原則,於是大大節省了系統資源。比如現在的軟體通常都能開啟若干種不同型別的檔案,這些讀寫操作通常都用動態連結庫來實現。在一次執行當中,一般只有一種型別的檔案將會被開啟。所以直到程式知道檔案的型別以後再載入相應的讀寫函式,而不是一開始就將所有的讀寫函式都載入,然後才發覺在整個程式中根本沒有用到它們。

三、動態連結庫的建立

由於動態連結庫函式的共享特性,它們不會被拷貝到可執行檔案中。在編譯的時候,編譯器只會做一些函式名之類的檢查。在程式執行的時候,被呼叫的動態連結庫函式被安置在記憶體的某個地方,所有呼叫它的程式將指向這個程式碼段。因此,這些程式碼必須實用相對地址,而不是絕對地址。在編譯的時候,我們需要告訴編譯器,這些物件檔案是用來做動態連結庫的,所以要用地址不無關程式碼(Position Independent Code (PIC))。

對gcc編譯器,只需新增上 -fPIC 標籤,如:

gcc -fPIC -c file1.c
gcc -fPIC -c file2.c
gcc -shared libxxx.so file1.o file2.o

注意到最後一行,-shared 標籤告訴編譯器這是要建立動態連結庫。這與靜態連結庫的建立很不一樣,後者用的是 ar 命令。也注意到,動態連結庫的名字形式為 “libxxx.so” 字尾名為 “.so”

四、動態連結庫的使用

使用動態連結庫,首先需要在編譯期間讓編譯器檢查一些語法與定義。

這與靜態庫的實用基本一樣,用的是 -Lpath 和 -lxxx 標籤。如:

gcc file1.o file2.o -Lpath -lxxx -o program.exe

編譯器會先在path資料夾下搜尋libxxx.so檔案,如果沒有找到,繼續搜尋libxxx.a(靜態庫)。

在程式執行期間,也需要告訴系統去哪裡找你的動態連結庫檔案。在UNIX下是通過定義名為 LD_LIBRARY_PATH 的環境變數來實現的。只需將path賦值給此變數即可。csh 命令為:

setenv LD_LIBRARY_PATH your/full/path/to/dll

一切安排妥當後,你可以用 ldd 命令檢查是否連線正常。

ldd program.exe


動態連結庫*.so的編譯與使用- -


動態庫*.so在linux下用c和c++程式設計時經常會碰到,最近在網站找了幾篇文章介紹動態庫的編譯和連結,總算搞懂了這個之前一直不太瞭解得東東,這裡做個筆記,也為其它正為動態庫連結庫而苦惱的兄弟們提供一點幫助。
1、動態庫的編譯

下面通過一個例子來介紹如何生成一個動態庫。這裡有一個標頭檔案:so_test.h,三個.c檔案:test_a.c、test_b.c、test_c.c,我們將這幾個檔案編譯成一個動態庫:libtest.so。

so_test.h:

#include
#include

void test_a();
void test_b();
void test_c();


test_a.c:

#include "so_test.h"
void test_a()
{
printf("this is in test_a...\n");
}


test_b.c:
#include "so_test.h"
void test_b()
{
printf("this is in test_b...\n");
}

test_a.c:

#include "so_test.h"
void test_c()
{
printf("this is in test_c...\n");
}

將這幾個檔案編譯成一個動態庫:libtest.so
$ gcc test_a.c test_b.c test_c.c -fPIC -shared -o libtest.so

2、動態庫的連結

在1、中,我們已經成功生成了一個自己的動態連結庫libtest.so,下面我們通過一個程式來呼叫這個庫裡的函式。程式的原始檔為:test.c。

test.c:

#include "so_test.h"
int main()
{
test_a();
test_b();
test_c();
return 0;

}

l 將test.c與動態庫libtest.so連結生成執行檔案test:

$ gcc test.c -L. -ltest -o test

l 測試是否動態連線,如果列出libtest.so,那麼應該是連線正常了

$ ldd test

l 執行test,可以看到它是如何呼叫動態庫中的函式的。
3、編譯引數解析
最主要的是GCC命令列的一個選項:
-shared 該選項指定生成動態連線庫(讓聯結器生成T型別的匯出符號表,有時候也生成弱連線W型別的匯出符號),不用該標誌外部程式無法連線。相當於一個可執行檔案

l -fPIC:表示編譯為位置獨立的程式碼,不用此選項的話編譯後的程式碼是位置相關的所以動態載入時是通過程式碼拷貝的方式來滿足不同程式的需要,而不能達到真正程式碼段共享的目的。

l -L.:表示要連線的庫在當前目錄中

l -ltest:編譯器查詢動態連線庫時有隱含的命名規則,即在給出的名字前面加上lib,後面加上.so來確定庫的名稱

l LD_LIBRARY_PATH:這個環境變數指示動態聯結器可以裝載動態庫的路徑。

l 當然如果有root許可權的話,可以修改/etc/ld.so.conf檔案,然後呼叫 /sbin/ldconfig來達到同樣的目的,不過如果沒有root許可權,那麼只能採用輸出LD_LIBRARY_PATH的方法了。
4、注意

呼叫動態庫的時候有幾個問題會經常碰到,有時,明明已經將庫的標頭檔案所在目錄 通過 “-I” include進來了,庫所在檔案通過“-L”引數引導,並指定了“-l”的庫名,但通過ldd命令察看時,就是死活找不到你指定連結的so檔案,這時你要作的就是通過修改LD_LIBRARY_PATH或者/etc/ld.so.conf檔案來指定動態庫的目錄。通常這樣做就可以解決庫無法連結的問題了。


相關文章