Linux-動態鏈接與靜態鏈接對比(動態庫和靜態庫)

一、庫的基礎概念:

在windows平臺和linux平臺下都大量存在着庫。本質上來說庫是一種可執行代碼的二進制形式,可以被操作系統載入內存執行。由於windows和linux的本質不同,因此二者庫的二進制是不兼容的。通俗的說就是把這些常用函數的目標文件打包在一起,提供相應函數的接口,便於程序員使用。在使用函數時,只需要包對應的頭文件即可。按照庫的使用方式又可分爲動態庫和靜態庫,在不同平臺下對應後綴也有所不同。
WINDOWS下:.dll 後綴爲動態庫,.lib 後綴爲靜態庫;
LINUX下:.so後綴爲動態庫,.a後綴爲靜態庫。

二、靜態庫與靜態鏈接

<1>靜態庫:

靜態庫可以簡單的看成一組目標文件的集合,即很多目標文件經過壓縮打包後形成的文件。比如在我們日常編程中,如果需要使用printf函數,就需要包stdio.h的庫文件,使用strlen時,又需要包string.h的庫文件,可是如果直接把對應函數源碼編譯後形成的.o文件直接提供給我們,將會對我們的管理和使用上造成極大不便,於是可以使用“ar”壓縮程序將這些目標文件壓縮在一起,形成libx.a靜態庫文件。
注:靜態庫命名格式:lib + “庫名稱”+ .a(後綴) 例:libadd.a就是一個叫add的靜態庫


<2>靜態鏈接:
對於靜態庫,程序在編譯鏈接時,將庫的代碼鏈接到可執行文件中,程序運行時不再需要靜態庫。在使用過程中只需要將庫和我們的程序編譯後的文件鏈接在一起就可形成一個可執行文件。

注:詳細關於靜態庫及靜態鏈接可參考《程序員的自我修養——鏈接、裝載與庫》第四章

通過一個例子來了解下如何將我們自己寫的頭文件和代碼同時進行編譯鏈接,最終生成可執行文件:
/////main.c/////

#include <stdio.h>
#include "add.h"

int main()
{
    int ret = add(3, 4);
    printf("3 + 4 = %d\n",ret);

    return 0;
}

/////add.c/////

#include "add.h"

int add( int x, int y)
{   
    return x + y;
}


/////add.h/////

#pragma once
#include <stdio.h>

int add( int x, int y);

/////Makefile/////

main : main.c libadd.a
    gcc main.c -L . -ladd -o main
    //-L爲指定路徑 .爲當前目錄下 -l+庫名字,編譯器可在指定目錄下自己尋找名爲add的庫文件

libadd.a : 
    gcc -c add.c -o add.o

    //ar -rc將多個編譯後的文件打包爲一個靜態庫文件
    ar -rc libadd.a add.o

.PHONY:clean
clean:
    rm main libadd.a
make後輸出截圖:

這裏寫圖片描述

<3>缺點:
1、內存和磁盤空間浪費:靜態鏈接方式對於計算機內存和磁盤的空間浪費十分嚴重。假如一個c語言的靜態庫大小爲1MB,系統中有100個需要使用到該庫文件,採用靜態鏈接的話,就要浪費進100M的內存,若數量再大,那浪費的也就更多。例如下圖:程序1和程序2都需要用到Lib.o,採用靜態鏈接的話,那麼物理內存中就會存放兩份對應此文件的拷貝。

這裏寫圖片描述

2、更新麻煩:比如一個程序20個模塊,每個模塊只有1MB,那麼每次更新任何一個模塊,用戶都得重新下載20M的程序。

三、動態庫與動態鏈接

<1>動態庫:程序在運行時纔去鏈接動態庫的代碼,多個程序共享庫的代碼。一個與動態庫鏈接的可執行文件僅僅包含它用到的函數入口地址的一個表,而不是外部函數所在目標文件的整個機器碼。

注:動態庫命名格式:lib + “庫名稱”+ .so(後綴) 例:libadd.so就是一個叫add的動態庫


<2>動態鏈接:由於靜態鏈接具有浪費內存和模塊更新困難等問題,提出了動態鏈接。基本實現思想是把程序按照模塊拆分成各個相對獨立部分,在程序運行時纔將他們鏈接在一起形成一個完整的程序,而不是像靜態鏈接那樣把所有的程序模塊都鏈接成一個單獨的可執行文件。所以動態鏈接是將鏈接過程推遲到了運行時才進行。
同樣,假如有程序1,程序2,和Lib.o三個文件,程序1和程序2在執行時都需要用到Lib.o文件,當運行程序1時,系統首先加載程序1,當發現需要Lib.o文件時,也同樣加載到內存,再去加載程序2當發現也同樣需要用到Lib.o文件時,則不需要重新加載Lib.o,只需要將程序2和Lib.o文件鏈接起來即可,內存中始終只存在一份Lib.o文件。

這裏寫圖片描述

>注:詳細關於動態庫及動態鏈接可參考《程序員的自我修養——鏈接、裝載與庫》第七章。

動態庫和動態鏈接的例子依然使用上面的代碼,輸出結果也相同,唯一需要改變的就是Makefile文件。
/////Makefile/////
main : main.c libadd.so
    gcc main.c -L . -ladd -o main

libadd.so : 
    gcc -fPIC -shared add.c -o libadd.so
    //-shared表示輸出結果是共享庫類型的  -fPIC表示使用地址無關代碼奇數來生產輸出文件

.PHONY:clean
clean:
    rm main libadd.so
注:<1>當我們生成可執行文件後,可使用ldd命令查看該可執行文件所依靠的動態庫。

這裏寫圖片描述


<2>前面提到windows和Linux下庫文件的後綴不同,更根本的原因在於二者文件格式都不同。可以通過file一個動態庫查看Linux下動態庫的文件類型其實是ELF格式。ELF動態鏈接文件被稱爲動態共享對象(DSO,Dynamic Shared Objects),簡稱共享對象;在windows下,動態鏈接文件被稱爲動態鏈接庫(Dynamic Linking Library),也就是.dll文件後綴的全稱


<3>優點:①毋庸置疑的就是節省內存;②減少物理頁面的換入換出;③在升級某個模塊時,理論上只需要將對應舊的目標文件覆蓋掉即可。新版本的目標文件會被自動裝載到內存中並且鏈接起來;④程序在運行時可以動態的選擇加載各種程序模塊,實現程序的擴展。

圖片截圖來自《程序員的自我修養——鏈接、裝載與庫》

發佈了55 篇原創文章 · 獲贊 192 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章