目錄
引入:
一:動靜態庫的介紹
1:庫的本質
2:庫的類別及優缺點
3:動態鏈接
4:靜態鏈接
二:頭文件和庫的查找
三:靜態庫的制作和使用
1:制作
2:指令打包
3:makefile打包
4:使用
①:直接gcc
②:僅-I
③:僅-I -L
④:三個選項齊全
四:動態庫的制作和使用
1:制作
2:指令打包
3:makefile打包
4:使用
五:系統查找動態庫
1:拷貝到默認搜索路徑下
2:修改環境變量修改
3:軟鏈接
4:動態庫路徑配置文件
六:動靜態庫的優先級規則
七:動態庫的加載
1:共享區的作用
2:編址
3:動態庫的加載過程
引入:
本文雖會詳細介紹動靜態庫的相關知識,但是gcc的相關語法以及編譯的細分4小步,和此篇博客的一大點的內容,也是在下面博客中說過了:Linux環境基礎開發工具->gcc/g++-CSDN博客
所以一定要看,不然肯定看不懂此篇博客
一:動靜態庫的介紹
1:庫的本質
函數所處的.c文件編譯后會形成.o文件,當多個.c形成多個.o后,這多個.o的集合就叫做庫!!
多個.o并不是放在那里就是一個庫,要分別用不同的方法才能讓這多個.o去形成動態庫或靜態庫!(在后面會講解)
2:庫的類別及優缺點
庫一般分為靜態庫和動態庫兩種:
①:靜態庫是指編譯鏈接時,把庫文件的代碼全部加入到可執行文件當中,因此生成的文件比較大,但在運行時也就不再需要庫文件了,靜態庫一般以.a為后綴。
②:動態庫與之相反,在編譯鏈接時并沒有把庫文件的代碼加入到可執行文件當中,而是在程序運行時由鏈接文件加載庫,這樣可以節省系統的開銷,動態庫一般以.so為后綴。
二者的優缺點:
動態鏈接:
?優點:省空間(磁盤的空間,內存的空間),且體積小,加載速度快。
?缺點:依賴動態庫,程序可移植性較差。靜態鏈接:
?優點:不依賴第三方庫,程序的可移植性較高。
?缺點:浪費空間。
3:動態鏈接
Q:那我們寫的.c 和 .cpp文件在gcc進行鏈接操作的時候,是鏈接的哪一種庫?
A:file指令就能知道
解釋:dynamically linked 意味著動態鏈接,所以鏈接的是動態庫!
4:靜態鏈接
我們還可以強行對code.o文件進行靜態鏈接到庫,指令:gcc -static
注:當然也可以直接對code.c進行強行的靜態鏈接,自由選擇即可
gcc code.o -o code.out_static -static//選項-static
我們靜態鏈接生成的可執行文件取名為?code.out_static,方便區別于code.out
此時我們就有兩個可執行文件了:
解釋:由靜態鏈接的缺點可知,靜態鏈接是把庫文件的代碼全部加入到可執行文件當中,所以這就是為什么我們的code.out_static的大小是861288,遠遠大于code.out的原因!
我們可以通過ldd來查看code.out_static是否真的被靜態鏈接了:
解釋:?信息告訴我們:這個程序是靜態鏈接的,沒有依賴任何動態庫(.so
?文件),因此無法列出動態庫依賴關系;所以這正好說明了的確進行了靜態鏈接
再通過file來看一下code.out_static:
解釋:statically linked 意味著靜態鏈接
總結:Linux中的編譯器gcc和g++默認都是動態鏈接的!需要靜態鏈接需要加 -static選項
二:頭文件和庫的查找
我們在使用庫中方法的時候,第一件事情,就是要找到方法所處的庫和聲明方法的頭文件,這一點,是毋庸置疑的!不然你的程序怎么跑?你用的方法,找不到聲明和實現,肯定出錯,而且是鏈接式報錯!
Q1:為什么是鏈接式報錯
A1:因為,鏈接的時候,就是找庫的時候,所以找不到庫,會報鏈接錯誤!
而當我們進行gcc的時候,直接生成了可執行程序,仿佛我們不用找庫,不用找頭文件?
其實不然,gcc只是幫我們做了這些事情,所以下面我們要了解gcc是怎么去查找庫和頭文件的!!!!
查找頭文件:
gcc會去兩個地方查找,一是默認搜索路徑,二是main函數所處的.c文件的當前目錄
查找庫:
gcc只會默認搜索路徑下查找
Q2:默認搜索路徑是什么?
A2:頭文件的默認搜索路徑就是Linux默認存儲頭文件的目錄的絕對地址;同理,庫的默認搜索路徑就是Linuxx默認存儲庫的目錄的絕對地址!
這就是為什么我們在Linux下的代碼使用了C方法或者C++方法的時候,只需gcc編譯,就能形成可執行程序,因為gcc能夠在這兩個搜索路徑下找到對應的庫和頭文件!
所以像C和C++的庫,都是已經被放在默認搜索路徑中的!
Q3:你講這么多,我理解了去默認搜索路徑下找頭文件和庫,但是你還說頭文件還能在main函數所處的.c的同級目錄下找,這是什么意思?
A3:這點在后面會驗證,但是現在也可以進行解釋,我們用一個在vs中編寫代碼的例子來解釋,
add.h
#pragma onceint add(int, int);
add.c
int add(int a, int b)
{return a + b;
}
main.c
#include<stdio.h>
#include"add.h"int main()
{int sum = add(1, 2);printf("%d", sum);return 0;
}
運行結果:
?
這個例子中,我們頭文件并不在什么默認搜索路徑下,而是就在我們的main.c的同級目錄下,如下:
但是我們的程序依舊正確的運行起來,這就證明了在編譯的時候,其還會在當前目錄下找頭文件!
而且在main.c中,我們包含add.h的格式,和包含stdio.h的格式不同:
#include<stdio.h>
#include"add.h"
這是官方對于默認搜索路徑下的頭文件,和在當前目錄下的頭文件,提供的不同的格式,這也證明了的確是有在當前目錄下找頭文件的行為
總結:
查找頭文件:
①:默認搜索路徑
②:當前目錄
查找庫:
①:默認搜索路徑下查找
而以上這些都是對標準庫和標準庫對應的頭文件的查找方法,這些是Linux已經存儲好的文件,所以按照上面的查找方法必然都能夠找到!
那請問如果是我們自己制作出的庫和頭文件呢,很顯然,其按照這些方法,是必然找不到的!因為自己做的庫和頭文件不會在默認的搜索路徑下!
在下面的第三和第四大點中,我會自己去實現一個靜態庫和動態庫,并且能夠正確的使用自己做出來的庫,所以不妨在這里,就先介紹一下,如果讓gcc找到我們自己定義的庫和頭文件!
三個選項:
-I
:指定頭文件搜索路徑 (大寫字母 i)-L
:指定庫文件搜索路徑?(大寫字母 L)-l
:指明在-L的
路徑下的哪一個庫??-l
(小寫字母 l)
當我們的gcc帶上這三個選項,并且在選項后面跟著正確的路徑或庫名字的時候,就能夠找到我們自己制作的庫和頭文件!因為我們已經清晰的告訴了gcc我們的頭文件和庫在哪!
三:靜態庫的制作和使用
1:制作
首先我們已經介紹了庫是一堆.o文件的集合,而靜態庫讓這堆.o集合形成靜態庫的方法就是打包!
指令為:
ar -rc//不存在則創建該.a靜態庫 存在則替換該.a靜態庫
現在有這么一個場景,我寫了一個加法和減法的.c和.h,如下:
add.h:
int add(int, int);
add.c:
int add(int a, int b)
{return a + b;
}
sub.h:
int sub(int, int);
sub.c:
int sub(int a, int b)
{return a - b;
}
現在有一個用戶,他需要我寫的加法和減法,所以我就要把我的這兩個方法打包為一個庫給他,然后再把頭文件也給他,這樣他就能直接使用我的方法了
但是任何的方法或者函數,都不是直接把.c給用戶,而是將其變成.o后打包成庫給用戶
原因主要是兩點:
①:保護我們的實現代碼(因為.o文件都是二進制文件,所以起到保護作用;)
②:讓客戶操作簡單(直接打包成庫給用戶,用戶直接使用即可,而不是還要手動的自己打包成庫)
所以現在我們先讓這兩個.c形成.o:
gcc -c add.c
gcc -c sub.c
下一步我們應該進行打包,但是我們可以先嘗試下不打包以體現對于用戶的不便性!
現在來了個用戶,其已經寫好了main.c了,就差我們把所需的東西給他了:
注:main.c在user的下級目錄
用戶的main.c如下:
//用戶的main.c#include<stdio.h>
#include"add.h"
#include"sub.h"int main()
{int sum = add(20,10);int dif = sub(20,10);printf("sum=%d dif=%d\n",sum,dif);return 0;
}
?所以既然我們不打包,我們就直接把兩個.o和兩個.h給他:
cp add.o ./user
cp sub.o ./user
cp add.h ./user
cp sub.h ./user
?
現在用戶就要開始形成自己的可執行程序了,所以其要先把自己的main.c形成.o:
gcc -c main.c //-c 代表把.c形成.o文件
然后現在.o文件都有了,下一步就是鏈接形成可執行程序了:
gcc main.o add.o sub.o //gcc形成可執行程序
?
成功的生成了a.out這個可執行程序,運行效果如下:
解釋:達到了用戶所需的效果
但是如果某個場景中,.o文件多達數百個,用戶在網站中下載我們這一大堆零散的.o文件,出現遺漏,那造成的后果不堪設想!
2:指令打包
所以為了避免這種場景,大家都會選擇一種做法:
將.o打包形成一個庫再放進lib目錄中,再將.h都放進一個include目錄中,最后再把lib和included都放進一個目錄中(lib就是庫的縮寫,include代表存放頭文件的目錄)
?
樹形圖如下:
所以指令如下:
ar -rc libcal.c add.o sub.o //讓兩個.o形成一個名為libcal.c的靜態庫
mkdir -p mathlib/lib //在當前目錄下創建一個mathlib/libm目錄
mkdir -p mathlib/include //在當前目錄下創建一個mathlib/include目錄
cp ./*h mathlib/include/ //將當前目錄下的所以.h文件拷貝到mathlib/include中
cp ./*o mathlib/lib/ //將當前目錄下的libcal.c靜態庫 拷貝到mathlib//lib中
所以,這就是為什么,往往你在網上找到一個mod或者小程序,你去下載的時候,會發下你其有很多文件夾,并且文件夾里面還有文件夾,本質就是和我們的做法大同小異!
注:我們生成這個樹狀圖的過程叫做"發布"!
3:makefile打包
當然,這些所有的指令,我們都可以全部在makefile中完成!當我們以后再要生成靜態庫以及組織頭文件和庫文件時就可以一步到位了,不至于每次重新生成的時候都要敲這么多命令,這也體現了Makefile的強大。
會用到一個新的指令make output ,叫做"發布",當我們需要發布一個庫的時候,就需要使用make output 指令,其內部一般會執行的就是我們上文說的:"將.o打包形成一個庫再放進lib目錄中,再將.h都放進一個include目錄中,最后再把lib和included都放進一個目錄中!"
另外,需要先make生成靜態庫,然后才能make output進行發布!
畢竟你連庫都沒有生成,談何發布?
makefile如下所示:
無注釋版:
mylib=libcal.a
CC=gcc$(mylib): add.o sub.oar -rc -o $(mylib) $^%.o: %.c$(CC) -c $<.PHONY: clean
clean:rm -f $(mylib) ./*.o.PHONY: output
output:mkdir -p mathlib/includemkdir -p mathlib/libcp ./*.h mathlib/includecp ./*.a mathlib/lib
注釋版本:?
# 定義靜態庫名稱
mylib=libcal.a# 定義使用的編譯器
CC=gcc# 默認目標:構建靜態庫
$(mylib): add.o sub.o# 將add.o和sub.o打包成靜態庫libcal.a# -rc 表示創建新庫(r)并添加文件(c)# -o 指定輸出文件名# $^ 表示所有依賴文件(add.o sub.o)ar -rc -o $(mylib) $^# 模式規則:從.c文件生成.o文件
%.o: %.c# 編譯C源文件生成目標文件# -c 表示只編譯不鏈接# $< 表示第一個依賴文件(%.c)$(CC) -c $<# 偽目標:清理生成的文件
.PHONY: clean
clean:# 刪除靜態庫和所有.o文件rm -f $(mylib) ./*.o# 偽目標:組織輸出目錄結構
.PHONY: output
output:# 創建include目錄mkdir -p mathlib/include# 創建lib目錄mkdir -p mathlib/lib# 復制所有.h文件到include目錄cp ./*.h mathlib/include# 復制所有.a文件到lib目錄cp ./*.a mathlib/lib
makefile的效果:
目前的狀態:
make后:
mak output后:
符合預期!
4:使用
談了這么多,也終于到使用我們的庫的時候了!當然,客戶肯定使用gcc來編譯我們發布的庫!
現在用戶來了:
其第一步就是把,mathlib這個目錄拿走(類似于下載),所以指令如下:
mv mathlib/ user/ //模擬用戶的下載行為
現在用戶已經準備好了.o,也有了mathlib這個目錄,其內部有庫有頭文件
現在我們知道其肯定是需要使用gcc的三個選項的,因為庫和頭文件都不在默認搜索路徑下,頭文件也不在main.c的同級目錄下!但是我們還是模擬一下錯誤的過程吧:
①:直接gcc
解釋:報錯找不到頭文件!因為gcc默認只在當前目錄和系統路徑中找頭文件
②:僅-I
解釋:報錯add
?和?sub
?函數未定義!因為雖然找到了頭文件(聲明了函數),但?未鏈接函數實現的庫(libcal.a
?或?libcal.so
),導致鏈接器找不到函數定義。
③:僅-I -L
解釋:報錯找不到庫!-lcal
?會讓鏈接器查找?libcal.a
?或?libcal.so
,但未用?-L
?指定庫路徑,鏈接器只在系統默認路徑(如?/usr/lib
)中查找,而你的庫在?mathlib/lib
?中。
這里涉及到一個庫的名字的獲取規則,我們的庫為libcal.a,但其實這個庫的名字為cal,因為去掉前綴lib,去掉后綴.a,因為任何庫都是lib前綴,任何靜態庫都是.a后綴 ,所以-I的時候要去掉!!
④:三個選項齊全
解釋:符合預期,未報錯!
四:動態庫的制作和使用
動態庫的制作和打包與靜態庫有些許的不同,共兩點:
①:gcc形成.o文件,需要加上 -fPIC選項
②:形成庫不再用ar指令打包,而是使用gcc的-shared選項即可打包
1:制作
先生成.o文件
gcc -fPIC -c add.c
gcc -fPIC -c sub.c
//動態庫生成.o 一定要帶-fPIC選項
2:指令打包
?對.o文件進行打包:
gcc -shared -o libcal.so add.o sub.o //動態庫-shared選項即可打包
?
然后將.o打包形成一個庫再放進lib目錄中,再將.h都放進一個include目錄中,最后再把lib和included都放進一個目錄中
gcc -shared -o libcal.so add.o sub.o //形成動態庫
mkdir -p mathlib/include //創建目錄
mkdir -p mathlib/lib //創建目錄
cp ./*.h mathlib/include //將所有頭文件拷貝到mathlib/include中
cp ./libcal.so mathlib/lib //將動態庫拷貝到mathlib/lib中
樹形圖如下:
和靜態庫一樣的套路,我們依舊換成makefile來進行
3:makefile打包
無注釋:
mylib=libcal.so
CC=gcc$(mylib): add.o sub.o$(CC) -shared -o $(mylib) $^%.o: %.c$(CC) -fPIC -c $<.PHONY: clean
clean:rm -rf $(mylib) ./*.o.PHONY: output
output:mkdir -p mathlib/includemkdir -p mathlib/libcp ./*.h mathlib/includecp ./*.so mathlib/lib
含注釋:
# 定義使用的編譯器
CC=gcc# 默認目標:構建動態庫
$(mylib): add.o sub.o# 將add.o和sub.o打包成動態庫libcal.so# -shared 表示生成動態鏈接庫# -o 指定輸出文件名# $^ 表示所有依賴文件(add.o sub.o)$(CC) -shared -o $(mylib) $^# 模式規則:從.c文件生成.o文件(用于動態庫需要-fPIC選項)
%.o: %.c# 編譯C源文件生成位置無關代碼(PIC)的目標文件# -fPIC 生成位置無關代碼(Position Independent Code)# -c 表示只編譯不鏈接# $< 表示第一個依賴文件(%.c)$(CC) -fPIC -c $<# 偽目標:清理生成的文件
.PHONY: clean
clean:# 刪除動態庫和所有.o文件rm -rf $(mylib) ./*.o# 偽目標:組織輸出目錄結構
.PHONY: output
output:# 創建include目錄mkdir -p mathlib/include# 創建lib目錄mkdir -p mathlib/lib# 復制所有.h文件到include目錄cp ./*.h mathlib/include# 復制所有.so文件到lib目錄cp ./*.so mathlib/lib
makefile效果:
make后:
make output后:
符合預期!
4:使用
不再演示三個選項缺失的報錯了,直接使用三個選項吧~
gcc main.c -I./mlib/include -L./mlib/lib -lcal
生成了a.out
執行a.out
解釋:竟然報錯了?!!報錯加載共享庫時出現錯誤,也就是找不到動態庫!
用ldd指令看一下鏈接的庫的信息
?
?這就是動態庫和靜態庫的區別!!
五:系統查找動態庫
a.out無法執行的原因是因為,操作系統找不到動態庫!
是的,不僅gcc需要找動態庫,OS也需要找動態庫!
Q1:那靜態庫的時候,為什么沒有報錯?OS不找靜態庫?
A1:靜態庫的特點就是已經寫在了代碼里,所以不需要找,代碼中就有,而動態庫,沒有寫在代碼里,所以,我們的三個選項只是告訴了gcc編譯器位置,而沒有告訴OS位置!
需要明白的是,gcc要找動態庫/靜態庫和頭文件,而OS只需要找動態庫!因為頭文件只有在編譯的時候才有用,在本文最開始 引入中的博客中談過,編譯的第一步預處理才需要包含頭文件,所以OS只需要找動態庫!
Q2:那OS找動態庫和gcc找動態庫的方式有什么區別?
A2:無任何區別,依舊是去庫的默認搜索路徑去找庫!因為你沒告訴OS,所以OS不知道!
Q3:第一大點中說過gcc是默認動態鏈接的,也就是是鏈接到動態庫的,那為什么我們寫代碼的時候,OS沒有報錯找不到動態庫?
A3:因為不管是什么庫,只要是標準庫,就已經被存放在了默認搜索路徑下的目錄中,OS找得到!!
Q4:那怎么告訴OS我們寫的動態庫在哪?
A4:四種方法!
四種方法,都會在使用方法前后進程ldd指令的對比,因為ldd指令可以查看os是否找到了動態庫!!!
1:拷貝到默認搜索路徑下
這種方法是最簡單的,也是最好理解的,既然你OS和gcc都要先去默認搜索路徑下找庫,那我干脆直接把庫放進默認的路徑中,但是這種方法需要sudo或者root才可以:
操作如下:
如果你是centos,則你:
sudo cp mathlib/lib/libcal.so /lib64
如果你是ubuntu,則你:
sudo cp mathlib/lib/libcal.so /lib/x86_64-linux-gnu/
因為不同版本下,OS對動態庫的默認搜索路徑不一樣~?
如何把1的操作去除:
以下每個操作后,都會去除上種方法的效果,避免影響后面的方法效果!
2:修改環境變量修改
和環境變量PATH類似,PATH是找可執行程序的路徑,而找動態庫的路徑也是可以修改對應的環境變量的
指令如下:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:xxxxx/mathlib/lib
//xxx代表你的mathlib所處的路徑
所以我的指令如下:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:mathlib/lib/
效果如下:
?
注:你是給?LD_LIBRARY_PATH 這個環境變量一個路徑,所以你填寫到? mathlib/lib/ 就嘚停止,而不是mathlib/lib/libcal.so!!
?
如何去除2的效果:
unset LD_LIBRARY_PATH
因為我的這個路徑本來就沒值,所以可以直接使用unset指令清空
但是如果你的環境變量里面有值,則你需要:
export LD_LIBRARY_PATH=$(echo $LD_LIBRARY_PATH | sed 's|:mathlib/lib||g')
或者直接關閉客戶端重啟即可
3:軟鏈接
sudo ln -s /home/wtt1/lesson4/user/mathlib/lib/libcal.so /lib/x86_64-linux-gnu/libcal.so
注:
①:格式:
ln -s <源文件絕對路徑> <目標位置完整路徑>
②:必須指定鏈接文件的完整路徑和名稱(不能只寫目錄路徑,精確到動態庫文件)
效果:
?
如何去除?
sudo rm /lib/x86_64-linux-gnu/libmyc.so
4:動態庫路徑配置文件
我們可以通過配置/etc/ld.so.conf.d/路徑下的文件,來讓OS找到動態庫!
/ld.so.conf.d中的ld譯為加載,so譯為動態庫,conf.d譯為配置文件的目錄
所以名字就很好理解,"加載動態庫的配置文件的目錄"
?
/etc/ld.so.conf.d/路徑下存放的全部都是以.conf為后綴的配置文件,而這些配置文件當中存放的都是路徑,系統會自動在/etc/ld.so.conf.d/路徑下找所有配置文件里面的路徑,之后就會在每個路徑下查找你所需要的庫。我們若是將自己庫文件的路徑也放到該路徑下,那么當可執行程序運行時,系統就能夠找到我們的庫文件了。
ls該目錄如下圖:
解釋:該路徑下全是.conf結尾的文件,而我們需要做的就是再創建一個.conf為后綴的文件,該文件的文件名隨便取,假設我就取wtt1.conf
所以我就在當前目錄創建一個wtt1.conf 然后將其mv到/etc/ld.so.conf.d/路徑下:
sudo mv wtt1.conf /etc/ld.so.conf.d/
?
Q:那文件的內容寫什么?
A:就寫你動態庫所處的路徑即可!
echo /home/wtt1/lesson4/user/mathlib/lib > /etc/ld.so.conf.d/wtt1.conf
//將動態庫所處的路徑 寫進/etc/ld.so.conf.d下的配置文件wtt1.conf
?此時ldd a.out 發現依舊沒有生效:
?
?因為還要執行sudo ldconfig 指令才行:
解釋:ldconfig ???指令會讓配置文件生效!
至此,4種讓OS找到動態庫的方法介紹完了!
官方下載的庫建議用第一種方法,自己實現的庫建議用第三z種方法
六:動靜態庫的優先級規則
①:如果我們同時提供動態庫和靜態庫,gcc默認使用的是動態庫?
解釋:可以在同時有t同名的動態庫和靜態庫下驗證,但是我們在第一大點中就已經驗證了,默認的gcc形成的程序,進行ldd指令或者file指令,提示消息都表明了其是動態鏈接!
②:如果我們非要靜態連接,我們必須使用static選項③:如果我們只提供的靜態庫,那我們的可執行程序也沒辦法,即使你不指名static,其也會對該庫進行靜態連接,但是程序不一定整體是靜態連接的,因為不止鏈接這一個庫
④:如果我們只提供動態庫,默認只能動態連接,非得靜態連接,會發生連接報錯
?
七:動態庫的加載
上面六點,我們介紹了這么多關于動靜態庫的知識,所以下面大致講解一下動靜態庫加載到內存中之后是如何影響進程的內核數據結構的!
1:共享區的作用
共享區就是磁盤中的動態庫被加載到內存,然后通過頁表映射到進程的進程地址空間的位置!所以動態庫又被叫做動態庫!
2:編址
Q:我們都知道進程地址空間存放的是虛擬地址,那請問進程地址空間的虛擬地址,是誰給他的?你任何一個值,首先得被初始化才有吧?
A:程序編譯期間就會形成虛擬地址,對你沒聽錯,程序還沒成為進程,還沒占用內存,其僅僅是在編譯期間,就會讓每行代碼都有自己的虛擬地址
比如下面是一個test.s的文件,也就是遠遠還沒有形成.o的時候,其就已經有地址了:
解釋:這是在反匯編下觀察到的結果,能夠看出其的確是有很多的地址,這個行為就叫作"編址"!
編址分為兩種:絕對編址(又叫作平坦模式)和相對編址(又叫作邏輯編址)
絕對編址就是像上圖中這樣,從一個地址開始,整個程序都是遞增式的地址,又稱為“平坦模式”!上圖中的401010 --->401015--->401019--->401020 是連續的,所以是絕對編址
而相對編址,每一個代碼塊會有一個起始地址,然后該代碼塊里面的每句代碼前面不是地址,而是偏移量,所以一句代碼的地址,就是該句代碼位于的代碼塊的起始地址+偏移量
所以現在我們知道了,一個.c中的所有代碼和數據和變量等等一切東西,都會在編譯期間,就為其分配好了虛擬地址!所以此時當程序加載到內存中的時候,由于其被加載到內存中,所以其就能得到自己在內存中的物理地址,所以現在即有了物理地址,又有了虛擬地址,所以能夠通過頁表映射到進程地址空間!
而我們知道靜態庫是存在于代碼里面的,編譯的時候,靜態庫就會在直接拷貝進main.c文件中,而動態庫卻是被加載到內存,然后映射到進程地址空間的共享區的,所以這里面分別涉及到了絕對編址和相對編址!
3:動態庫的加載過程
靜態庫在編譯鏈接時,庫中所有代碼已經被拷貝到了用這個庫的main.c函數中,所以靜態庫會隨著.c文件一起被采取絕對編址的方式進行編址。當程序加載時,操作系統直接按照形成的可執行文件中絕對編址得到的虛擬地址,將靜態庫的代碼和數據段映射到進程的進程地址空間中!頁表將這些虛擬地址轉換為物理內存地址,形成映射!
動態庫不隨mian.c文件被絕對編址,其是位于磁盤中的一份獨立文件,而且動態庫通過-fPIC選項編譯,所以其在編譯的時候,在內部使用相對偏移進行編址。加載時,操作系統將動態庫的代碼映射到進程地址空間的共享區。而因為其是響度編址,所以任何方法都會有一個起始地址,方法中的語句都會有一個偏移量,所以當你的代碼中調用的庫的函數的時候,此時,當cpu讀取到這行調用庫方法的代碼時,其會得到一個起始地址和偏移量,然后跳轉到共享區對應的虛擬地址處,然后再通過該虛擬地址和頁表找到在內存中的具體實現方法,從而實現調用!