庫的概念:一組.o文件的集合,或者說.obj文件(windows下)的集合,也就是鏈接生成可執(zhí)行文件時(shí)用到的文件
在我的理解里,庫就是別人寫的代碼,比如庫函數(shù),第三方庫,包括之后會(huì)用到的網(wǎng)絡(luò)庫,這些都是別人寫的,我們只是拿過來使用而已
那為什么我們要使用別人的代碼呢?
因?yàn)椋簽榱碎_發(fā)效率和代碼的健壯性
那我們要如何使用別人的庫?
- 借助庫、開源代碼、和網(wǎng)絡(luò)。下面我們主要了解庫的使用
- 如果別人要用我們的功能,我們又不想給源碼,就可以把源碼打包成一個(gè)庫給對(duì)方用
庫的種類:
鏈接分為動(dòng)態(tài)鏈接和靜態(tài)鏈接,庫分為動(dòng)態(tài)庫和靜態(tài)庫。靜態(tài)鏈接是一個(gè)過程,這個(gè)過程中用到的庫就是靜態(tài)庫,動(dòng)態(tài)鏈接同理
使用三方庫時(shí)的鏈接過程:
鏈接過程簡(jiǎn)單概括為地址和空間分配,符號(hào)解析和代碼重定位
符號(hào)指的是變量,函數(shù)名等,符號(hào)解析則是將符號(hào)的定義和符號(hào)的引用建立聯(lián)系
重定位我簡(jiǎn)單理解為可以準(zhǔn)確的運(yùn)行我們的程序,比如一個(gè)函數(shù),我們可以精準(zhǔn)的找到函數(shù)入口的地址并進(jìn)行運(yùn)行
符號(hào)解析后生成符號(hào)表,linux下查看符號(hào)表命令:
readelf -s 文件名.o
代碼的重定位:我們運(yùn)行的指令都有其對(duì)應(yīng)的地址,比如我們進(jìn)入一個(gè)函數(shù)就是跳到一個(gè)表示函數(shù)入口的地址,代碼變化指令也會(huì)跟著變化,地址自然就變化了,比如前面一開始有4條代碼,修改后有5條,那對(duì)應(yīng)的指令和地址可能都要發(fā)生變化,函數(shù)入口就變化了,此時(shí)的代碼里重新計(jì)算地址的過程就叫做重定位。如果有多個(gè)模塊,成千上萬行代碼,修改這個(gè)地址的工作就是龐大的,早期還是人工改,現(xiàn)在都交給鏈接器了
靜態(tài)鏈接概念:
- 靜態(tài)鏈接:是指編譯階段把靜態(tài)庫的代碼加入到可執(zhí)行文件中,或者說把用到的函數(shù)的全部鏈接到可執(zhí)行文件中(但是鏈接器是以文件為單位進(jìn)行操作的,比如要用printf就得鏈接所有包含printf的文件)。這樣生成的可執(zhí)行文件不需要借助外部的庫,生成后可以直接使用,這就是靜態(tài)鏈接。缺點(diǎn)是文件比較大,每次更新都得重新編譯,優(yōu)點(diǎn)是運(yùn)行較快
- 簡(jiǎn)言之:把需要的代碼和數(shù)據(jù)從庫全拷貝到當(dāng)前代碼
動(dòng)態(tài)鏈接概念:
- 動(dòng)態(tài)鏈接:是什么時(shí)候要用到庫里的東西就什么時(shí)候去找,存的也只是索引和相關(guān)的部分信息,所以占用空間不大。好處是占用空間小,所有的程序可以共用同一個(gè)庫,更新也比較方便,缺點(diǎn)就是比較慢。如果我們有及時(shí)上百個(gè)進(jìn)程都用了同一個(gè)庫,那相比靜態(tài)鏈接就大大節(jié)省了空間
- 簡(jiǎn)言之:動(dòng)態(tài)庫可以被多個(gè)進(jìn)程共享,與進(jìn)程地址空間里的共享區(qū)有關(guān)
gcc -o 命令默認(rèn)動(dòng)態(tài)鏈接,加上-static選項(xiàng)就表示靜態(tài)鏈接,可通過file命令查看鏈接屬性
#includeint main()
{int a=1;
printf("%d\n",a);
return 0;
}
ldd命令,查看程序或者庫文件所依賴的共享庫,即打印動(dòng)態(tài)鏈接依賴的庫列表
下圖中:libc.so.6,把lib和so去掉,中間剩下的c就是庫名,.so就表示動(dòng)態(tài)庫,.a表示靜態(tài)庫,.6是主版本號(hào)
對(duì)于靜態(tài)鏈接與動(dòng)態(tài)鏈接的總結(jié):
- 靜態(tài)鏈接會(huì)拷貝代碼和數(shù)據(jù),再將其鏈接進(jìn)可執(zhí)行文件,因?yàn)殒溄悠鞯牟僮鲉卧质俏募造o態(tài)的鏈接的文件一般都較大,優(yōu)點(diǎn)是快(不用去庫里找東西自然快),不需要依賴外部(庫丟失了也能跑)。但是不好更新,每次更新都得重新編譯,比如包含了這個(gè)庫里的某個(gè)函數(shù)的文件就全得更新
- 動(dòng)態(tài)鏈接就是啥時(shí)候要就啥時(shí)候找,鏈接器看到動(dòng)態(tài)鏈接的符號(hào)也會(huì)去找,只不過不分配地址,重定位的操作等到了程序裝載時(shí)才做。但因?yàn)槭峭ㄟ^索引去找,所以多個(gè)進(jìn)程可以共享一個(gè)庫(庫丟失就跑不了了),一些場(chǎng)景下可以大大節(jié)省空間資源,更新時(shí)也方便,只用編譯相對(duì)應(yīng)的模塊
下面的動(dòng)靜態(tài)庫生成剖析通過下面四個(gè)函數(shù)來驗(yàn)證:
add.h
#pragma once
int add(int x,int y);
add.c
#include"add.h"
int add(int x,int y)
{return x+y;
}
sub.h
#pragma once
int sub(int x,int y);
sub.c
#include"sub.h"
int sub(int x,int y)
{return x-y;
}
下面以一個(gè)例子說明生成靜態(tài)庫的操作,我們利用ar工具把兩個(gè)C文件打包成一個(gè)靜態(tài)庫
。靜態(tài)庫里放.o文件,可以做到不暴露源碼,再建一個(gè)目錄放頭文件,告訴使用者里面有哪些方法可以用
靜態(tài)庫的打包過程:
關(guān)于ar命令的補(bǔ)充:
ar
命令是gnu的歸檔工具,常用于將目標(biāo)文件打包為靜態(tài)庫,下面我們使用ar
命令的-r
選項(xiàng)和-c
選項(xiàng)進(jìn)行打包
-r
(replace):若靜態(tài)庫文件當(dāng)中的目標(biāo)文件有更新,則用新的目標(biāo)文件替換舊的目標(biāo)文件-c
(create):建立靜態(tài)庫文件-t
:列出靜態(tài)庫中的文件-v
(verbose):顯示詳細(xì)的信息
打包流程:
- 第一步:讓所有源文件生成對(duì)應(yīng)的目標(biāo)文件,即gcc -c add.c -o add.o等操作
- 第二步:使用ar命令將所有目標(biāo)文件打包為靜態(tài)庫,即ar -rc …等操作
- 第三步:將頭文件和生成的靜態(tài)庫組織起來,即cp add.h sub.h …等操作
- 第四步(可有可無):編寫Makefile后,只需一個(gè)
make
就能生成所有源文件對(duì)應(yīng)的目標(biāo)文件進(jìn)而生成靜態(tài)庫
生成的靜態(tài)庫名為libmymath.a.1,后面的1表示版本號(hào),建議我們自己打包庫的時(shí)候去掉版本號(hào),不然后面使用的時(shí)候會(huì)顯示找不到庫…(也不知道為啥會(huì)找不到,網(wǎng)上查閱資料后發(fā)現(xiàn)靜態(tài)庫一般都把版本號(hào)寫在文件里,再通過strings+文件里添加字符串version來標(biāo)識(shí)文件是哪個(gè)版本,也有利用命令行參數(shù)來打印版本號(hào)的)
gcc -o main main.c -I ./include -L ./lib -lmymath -static
//-I指定去哪找頭文件,-L指定去哪找?guī)煳募?l+庫名表示去找哪個(gè)庫
//因?yàn)槟J(rèn)路徑的緣故,gcc編譯C代碼都不用加-I -L -l等選項(xiàng)
多個(gè)進(jìn)程可以共享一個(gè)多態(tài)庫,如下圖:
動(dòng)態(tài)庫上面提到是要的時(shí)候再去找,所以多個(gè)進(jìn)程用動(dòng)態(tài)庫時(shí)用的都是同一份代碼,合理的節(jié)省了資源
-fPIC作用于編譯時(shí)期,產(chǎn)生與位置無關(guān)的代碼,編譯后的代碼用的是相對(duì)地址
用相對(duì)地址的原因
:共享庫加載進(jìn)內(nèi)存的位置是不確定的,如果不加-fPIC選項(xiàng),可能生成帶有絕對(duì)位置的代碼,那鏈接器鏈接時(shí)可能就需要去重新定位各個(gè)符號(hào)的位置,那共享庫的代碼可能就發(fā)生了變化,我們的程序此時(shí)就要去維護(hù)這段發(fā)生了變化的代碼,維護(hù)進(jìn)行的操作就是拷貝一份,那動(dòng)態(tài)鏈接的作用就不大了(動(dòng)態(tài)鏈接本來是要用的時(shí)候直接用庫里的,現(xiàn)在我程序自身要維護(hù)一份更改后的代碼,顯然是不合理的),所以與位置無關(guān)的代碼其實(shí)就是使用了相對(duì)地址的代碼,這么做與elf文件的格式有關(guān)(借助了elf文件里面的“段”)??梢院?jiǎn)單理解為規(guī)定了一根線,關(guān)于地址的坐標(biāo)都是相對(duì)于這根線的,不管我程序加載到內(nèi)存的哪個(gè)位置,只要通過計(jì)算這根線到當(dāng)前位置的距離就可以獲取到(算出)正確的位置。真正清晰的理解需要了解elf文件的格式,編譯原理、鏈接器的相關(guān)知識(shí)
動(dòng)態(tài)庫的打包相對(duì)于靜態(tài)庫來說有一點(diǎn)點(diǎn)差別,但大致相同,我們還是利用上面的四個(gè)文件進(jìn)行打包演示:
動(dòng)態(tài)庫的打包流程:
第一步:讓所有源文件生成對(duì)應(yīng)的目標(biāo)文件
此時(shí)用源文件生成目標(biāo)文件時(shí)需要攜帶
-fPIC
選項(xiàng)
-fPIC
(position independent code):產(chǎn)生位置無關(guān)碼說明一下:
- -fPIC作用于編譯階段,告訴編譯器產(chǎn)生與位置無關(guān)的代碼,此時(shí)產(chǎn)生的代碼中沒有絕對(duì)地址,全部都使用相對(duì)地址,從而代碼可以被加載器加載到內(nèi)存的任意位置都可以正確的執(zhí)行。這正是共享庫所要求的,共享庫被加載時(shí),在內(nèi)存的位置不是固定的
- 如果不加-fPIC選項(xiàng),則加載.so文件的代碼段時(shí),代碼段引用的數(shù)據(jù)對(duì)象需要重定位,重定位會(huì)修改代碼段的內(nèi)容,這就造成每個(gè)使用這個(gè).so文件代碼段的進(jìn)程在內(nèi)核里都會(huì)生成這個(gè).so文件代碼段的拷貝,并且每個(gè)拷貝都不一樣,取決于這個(gè).so文件代碼段和數(shù)據(jù)段內(nèi)存映射的位置
- 不加-fPIC編譯出來的.so是要在加載時(shí)根據(jù)加載到的位置再次重定位的,因?yàn)樗锩娴拇aBBS位置無關(guān)代碼。如果該.so文件被多個(gè)應(yīng)用程序共同使用,那么它們必須每個(gè)程序維護(hù)一份.so的代碼副本(因?yàn)?so被每個(gè)程序加載的位置都不同,顯然這些重定位后的代碼也不同,當(dāng)然不能共享)
- 我們總是用-fPIC來生成.so,但從來不用-fPIC來生成.a。但是.so一樣可以不用-fPIC選項(xiàng)進(jìn)行編譯,只是這樣的.so必須要在加載到用戶程序的地址空間時(shí)重定向所有表目
第二步:使用-shared選項(xiàng)將所有目標(biāo)文件打包為動(dòng)態(tài)庫
- 與生成靜態(tài)庫不同的是,生成動(dòng)態(tài)庫時(shí)我們不必使用ar命令,我們只需使用gcc的-shared選項(xiàng)即可
gcc -shared -o libcal.so add.o sub.o
第三步:將頭文件和生成的動(dòng)態(tài)庫組織起來
- 與生成靜態(tài)庫時(shí)一樣,為了方便別人使用,在這里我們可以將
add.h
和sub.h
這兩個(gè)頭文件放到一個(gè)名為include的目錄下,將生成的動(dòng)態(tài)庫文件libcal.so
放到一個(gè)名為lib的目錄下,然后將這兩個(gè)目錄都放到mlib下,此時(shí)就可以將mlib給別人使用了第四步:使用Makefile
- 當(dāng)然,生成動(dòng)態(tài)庫也可以將上述所要執(zhí)行的命令全部寫到Makefile當(dāng)中,后續(xù)當(dāng)我們要生成動(dòng)態(tài)庫以及組織頭文件和庫文件時(shí)就可以一步到位了
- 編寫Makefile后,只需一個(gè)
make
就能生成所有源文件對(duì)應(yīng)的目標(biāo)文件進(jìn)而生成動(dòng)態(tài)庫
- 一個(gè)
make output
就能將頭文件和動(dòng)態(tài)庫組織起來
我們使用下面main.c的代碼作為例子:
//main.c
#include#include"add.h"
#include"sub.h"
int main()
{int a=add(0,1);
printf("%d\n",a);
return 0;
}
你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機(jī)房具備T級(jí)流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級(jí)服務(wù)器適合批量采購,新人活動(dòng)首月15元起,快前往官網(wǎng)查看詳情吧