前言
學(xué)習(xí)Linux動態(tài)鏈接庫是一個繞不開的話題,我們今天就一起來看一下什么是動態(tài)鏈接庫、動態(tài)鏈接庫有什么好處、如何編譯出一個動態(tài)鏈接庫等幾個關(guān)于動態(tài)鏈接庫的幾個基本概念,廢話少說咱們直接開始!
鏈接庫是什么?
當(dāng)使用 C 編程語言編寫一個應(yīng)用程序時,我們不可能每個程序都從最底層開始編寫,這樣的話無論是學(xué)習(xí)還是工作,都會帶來很多的不便,于是庫就出現(xiàn)了。
我們可以把庫理解成前輩給我們寫好的可以直接拿來用的、成熟的代碼。本質(zhì)上來所庫是一種可執(zhí)行的二進(jìn)制文件,可以直接被操作系統(tǒng)載入內(nèi)存執(zhí)行,庫有兩種:靜態(tài)庫(.a、.lib)和動態(tài)庫(.so、.dll)。
你的代碼通常有多個庫文件,庫文件是計(jì)算機(jī)中給我們提供的一類函數(shù)、變量或類,我們無需知道他們是具體是怎么實(shí)現(xiàn)的,我們只需要關(guān)心它是怎么使用的,比如需要哪些參數(shù)、實(shí)現(xiàn)什么功能、返回值是什么樣的等等。
本質(zhì)上來說庫是一種可執(zhí)行代碼的二進(jìn)制形式,可以被操作系統(tǒng)載入內(nèi)存執(zhí)行。

靜態(tài)鏈接庫
靜態(tài)鏈接庫是指編譯鏈接時,把庫文件的代碼全部加入到可執(zhí)行文件中,因此生成的文件比較大,但在運(yùn)行時也就不再需要庫文件了。其后綴名一般為 .a
。
我們需要注意的是靜態(tài)庫是會隨著編譯一起被編譯到 .o
文件中的,即一旦程序編譯靜態(tài)庫與匯編生成的目標(biāo)文件一起鏈接為可執(zhí)行文件,那么靜態(tài)庫必定跟.o文件格式相似,只有這樣才能和目標(biāo)文件成功鏈接。
靜態(tài)鏈接庫的特點(diǎn)
-
靜態(tài)庫對函數(shù)庫的鏈接是放在編譯時期完成的。
-
程序在運(yùn)行時與函數(shù)庫再無瓜葛,移植方便。
-
浪費(fèi)空間和資源,因?yàn)樗邢嚓P(guān)的目標(biāo)文件與牽涉到的函數(shù)庫被鏈接合成一個可執(zhí)行文件。
動態(tài)鏈接庫
通過上面對靜態(tài)鏈接庫的介紹我們其實(shí)對庫應(yīng)該已經(jīng)有個概念了,既然有靜態(tài)鏈接庫那肯定就存在動態(tài)的鏈接庫,那什么是動態(tài)鏈接庫呢?我們一起來看一下!
我們知道靜態(tài)鏈接庫會占用很多不必要的資源,那我們就能想到動態(tài)鏈接庫的第一個特點(diǎn)肯定就是節(jié)省資源。
動態(tài)庫在程序編譯時并不會像靜態(tài)鏈接庫那樣被連接到目標(biāo)代碼中,而是在程序運(yùn)行是才被載入。不同的應(yīng)用程序如果調(diào)用相同的庫,那么在內(nèi)存里只需要有一份該共享庫的實(shí)例,規(guī)避了空間浪費(fèi)問題。動態(tài)庫在程序運(yùn)行是才被載入,也解決了靜態(tài)庫對程序的更新、部署和發(fā)布頁會帶來麻煩。用戶只需要更新動態(tài)庫即可,增量更新。
動態(tài)庫一般后綴名為 .so
,gcc/g++在編譯時默認(rèn)使用動態(tài)庫。無論靜態(tài)庫,還是動態(tài)庫,都是由 .o
文件創(chuàng)建的。
動態(tài)庫的編譯
下面通過一個例子來介紹如何生成一個動態(tài)庫。建一個頭文件:dynamic.h
三個.cpp
文件:dynamic_a.cpp
、dynamic_b.cpp
, dynamic_c.cpp
我們將這幾個文件編譯成一個動態(tài)庫:libdynamic.so
。
dynamic.h
#ifndef?__DYNAMIC_H_
#define?__DYNAMIC_H_
#include?
void?dynamic_a();
void?dynamic_b();
void?dynamic_c();
#endif
dynamic_a.cpp
#include"dynamic.h"
void?dynamic_a()
{
??cout<<"this?is?in?dynamic_a?"<<endl;
}
dynamic_b.cpp
#include"dynamic.h"
void?dynamic_b()
{
??cout<<"this?is?in?dynamic_b?"<<endl;
}
dynamic_c.cpp
#include"dynamic.h"
void?dynamic_c()
{
??cout<<"this?is?in?dynamic_c?"<<endl;
}
將這幾個文件編譯成動態(tài)庫libdynamic.so
。編譯命令如下:
g++?dynamic_a.cpp?dynamic_b.cpp?dynamic_c.cpp?-fPIC?-shared?-o?libdynamic.so
參數(shù)說明:-shared
:該選項(xiàng)指定生成動態(tài)連接庫-fPIC
:表示編譯為位置獨(dú)立的代碼,不用此選項(xiàng)的話編譯后的代碼是位置相關(guān)的,所以動態(tài)載入時是通過代碼拷貝的方式來滿足不同進(jìn)程的需要,而不能達(dá)到真正代碼段共享的目的。
在上面的部分,我們已經(jīng)生成了一個libdynamic.so
的動態(tài)鏈接庫,現(xiàn)在我們用一個程序來調(diào)用這個動態(tài)鏈接庫。
main.cpp
#include"dynamic.h"
int?main()
{
??dynamic_c();
??dynamic_c();
??dynamic_c();
??return?0;
}
將main.cpp
與libdynamic.so
鏈接成一個可執(zhí)行文件main
,命令如下:
g++?main.cpp?-L.?-ldynamic?-o?main
參數(shù)說明:-L
:表示要連接的庫在當(dāng)前目錄中-ldynamic
:編譯器查找動態(tài)連接庫時有隱含的命名規(guī)則,即在給出的名字前面加上lib,后面加上.so
來確定庫的名稱。
測試可執(zhí)行程序main
是否已經(jīng)鏈接的動態(tài)庫libdynamic.so
,如果列出了libdynamic.so
,那么就說明正常鏈接了。可以執(zhí)行以下命令:
ldd?main
如果運(yùn)行:
./main
出現(xiàn)錯誤:
error?while?loading?shared?libraries:?libdynamic.so:?cannot?open?shared?object?file:?No?such?file?or?directory
錯誤原因:
ld
提示找不到庫文件,而庫文件就在當(dāng)前目錄中。
鏈接器ld
默認(rèn)的目錄是/lib
和/usr/lib
,如果放在其他路徑也可以,需要讓ld
知道庫文件在哪里。
解決方法1:
編輯/etc/ld.so.conf
文件,在新的一行中加入庫文件所在目錄;比如筆者應(yīng)添加:/home/neu/code/Dynamic_library
sudo?ldconfig
目的是用ldconfig
加載,以更新/etc/ld.so.cache
文件。
靜態(tài)庫的編譯
就以以上代碼演示,最好把生成的動態(tài)庫的東西全部刪掉。
編譯靜態(tài)庫
g++?-c?dynamic_a.cpp?dynamic_b.cpp?dynamic_c.cpp??
使用ar命令創(chuàng)建靜態(tài)庫文件(把目標(biāo)文檔歸檔)
ar?cr?libstatic.a?dynamic_a.o?dynamic_b.o?dynamic_c.o??//cr標(biāo)志告訴ar將object文件封裝(archive)
參數(shù)說明:
d 從指定的靜態(tài)庫文件中刪除文件
m 把文件移動到指定的靜態(tài)庫文件中
p 把靜態(tài)庫文件中指定的文件輸出到標(biāo)準(zhǔn)輸出
q 快速地把文件追加到靜態(tài)庫文件中
r 把文件插入到靜態(tài)庫文件中
t 顯示靜態(tài)庫文件中文件的列表
x 從靜態(tài)庫文件中提取文件
a 把新的目標(biāo)文件(*.o)添加到靜態(tài)庫文件中現(xiàn)有文件之后
使用nm -s 命令來查看.a文件的內(nèi)容
nm?-s?libstatic.a?
鏈接靜態(tài)庫
g++?main.cpp?-lstatic?-L.?-static?-o?main//這里的-static選項(xiàng)是告訴編譯器,hello是靜態(tài)庫也可以用
//g++?main.cpp?-lstatic?-L.??-o?main?
執(zhí)行以下命令,因?yàn)楣P者還是用的動態(tài)庫的代碼,所以結(jié)果一樣
./main
動態(tài)鏈接庫的優(yōu)點(diǎn)
- 節(jié)省資源;
- 可以實(shí)現(xiàn)進(jìn)程間資源共享;
- 更新、部署、發(fā)布簡單;
- 可以控制動態(tài)庫的加載時間(何時加載);
關(guān)于第二點(diǎn)我們該如何理解呢?進(jìn)程間的資源共享就是說,某個程序的在運(yùn)行中要調(diào)用某個動態(tài)鏈接庫函數(shù)的時候,操作系統(tǒng)首先會查看所有正在運(yùn)行的程序,看在內(nèi)存里是否已有此庫函數(shù)的拷貝了。如果有,則讓其共享那一個拷貝,只有沒有才鏈接載入。
這樣的模式雖然會帶來一些 “動態(tài)鏈接” 額外的開銷,卻大大的節(jié)省了系統(tǒng)的內(nèi)存資源。C的標(biāo)準(zhǔn)庫就是動態(tài)鏈接庫,也就是說系統(tǒng)中所有運(yùn)行的程序共享著同一個C標(biāo)準(zhǔn)庫的代碼段。
結(jié)語
我們在編寫程序的時候,可以根據(jù)需求選擇什么時候或者什么情況下,鏈接載入哪個動態(tài)鏈接庫函數(shù)。你可以有一個相當(dāng)大的軟件,但每次運(yùn)行的時候,由于不同的操作需求,只有一小部分程序被載入內(nèi)存。
所有的函數(shù)本著 “有需求才調(diào)入” 的原則,于是大大節(jié)省了系統(tǒng)資源。比如現(xiàn)在的軟件通常都能打開若干種不同類型的文件,這些讀寫操作通常都用動態(tài)鏈接庫來實(shí)現(xiàn)。
在一次運(yùn)行當(dāng)中,一般只有一種類型的文件將會被打開。所以直到程序知道文件的類型以后再載入相應(yīng)的讀寫函數(shù),而不是一開始就將所有的讀寫函數(shù)都載入,然后才發(fā)覺在整個程序中根本沒有用到它們。
?
審核編輯:湯梓紅
評論