目錄
一、動靜態庫的概念
二、靜態庫的打包與使用
2.1 靜態庫的打包
2.2 靜態庫的使用
三、動態庫的打包與使用
3.1 動態庫的打包
3.2 動態庫的使用
3.3 運行動態庫的四種方法
四、總makefile
一、動靜態庫的概念
靜態庫: Linux下,以.a為后綴的文件。程序在編譯鏈接的時候把庫的代碼鏈接到可執行文件中。程序運行的時候將不再需要靜態庫。本質是在編譯時把靜態庫中的代碼(不是一次性加載,而是分頁加載)復制到了進程的的代碼區中。
動態庫: Linux下,以.so為后綴的文件。程序在運行的時候才去鏈接動態庫的代碼,在可執行程序裝載或運行時,由操作系統的裝載程序加載庫,多個程序共享使用庫的代碼。一個與動態庫鏈接的可執行文件僅僅包含它用到的函數入口地址的一個表,而不是外部函數所在目標文件的整個機器碼。
靜態鏈接:?將庫中的相關代碼復制進可執行程序中的過程。
動態鏈接:?在可執行文件開始運行以前,外部函數的機器碼由操作系統從磁盤上的該動態庫中復制到內存中。
鏈接的本質: 使.o文件可以找到要調用的函數的位置
庫文件名稱:?比如libc.so,去掉前綴lib和后綴.so,剩下的就是庫名。libhello.a的庫名就是hello。
頭文件gcc的默認搜索路徑是 /usr/include
庫文件的默認搜索路徑是 /usr/lib64把文件拷貝到系統的默認路徑下就叫做庫的安裝,拷貝之后就不用 -I 和 -L了
實例演示:?分別使用靜態鏈接和動態鏈接編譯生成兩個可執行程序,比較兩個程序的大小
使用gcc靜態鏈接編譯時,命令要帶上**-static** 選項,如下:
gcc -o test test.c -static
?
可以看到的是,使用靜態庫靜態鏈接成的可執行程序比動態鏈接生成的可執行程序要大很多。
我們還可以通過file命令查看文件的鏈接屬性:?還可以通過ldd?命令查看可執行程序的依賴庫,動態鏈接生成的可執行程序才有依賴庫,靜態鏈接升序的可執行程序不依賴任何庫文件,因為庫文件的代碼已經復制進可執行程序了。
因為這里是動態鏈接,不僅要讓編譯器動態庫的路徑,還要讓操作系統知道,所以這里我們需要導入一個環境變量LD_LIBRARY_PATH,如下:
export LD_LIBRARY_PATH=/home/dgz/linux/lesson26/lib/uselib/output/lib
執行完之后
總結動靜態庫的優缺點
靜態庫
- 優點: 程序運行的時候將不再需要靜態庫,在可執行程序中已經具備了所有執行程序所需要的任何東西,在執行的時候運行速度快。
- 缺點:一是浪費空間,因為每個可執行程序中對所有需要的目標文件都要有一份副本,所以如果多個程序對同一個目標文件都有依賴,如多個程序中都調用了printf()函數,則這多個程序中都含有printf.o,所以同一個目標文件都在內存存在多個副本;另一方面就是更新比較困難,因為每當庫函數的代碼修改了,這個時候就需要重新進行編譯鏈接形成可執行程序。
動態庫
- 優點: 動態鏈接使得可執行文件更小,節省了磁盤空間。操作系統采用虛擬內存機制允許物理內存中的一份動態庫被要用到該庫的所有進程共用,節省了內存和磁盤空間.動態鏈接的優點顯而易見,就是即使需要每個程序都依賴同一個庫,但是該庫不會像靜態鏈接那樣在內存中存在多分,副本,而是這多個程序在執行時共享同一份副本;另一個優點是,更新也比較方便,更新時只需要替換原來的目標文件,而無需將所有的程序再重新鏈接一遍。當程序下一次運行時,新版本的目標文件會被自動加載到內存并且鏈接起來,程序就完成了升級的目標
- 缺點: 程序運行的時候依賴動態庫,? 據估算,動態鏈接和靜態鏈接相比,性能損失大約在5%以下。經過實踐證明,這點性能損失用來換區程序在空間上的節省和程序構建和升級時的靈活性是值得的。
二、靜態庫的打包與使用
2.1 靜態庫的打包
靜態庫打包:?本質其實就是將代碼編譯成.o的二進制文件,然后進行打包。
為了更好地演示這個過程,我創建了mymath.c、mymath.h、myprint.c和myprint.h四個文件,內容分別如下:
mymath.c
?
#include "mymath.h"
int Add_(int a,int b)
{return a+b;
}
mymath.h
?
#pragma once
#include <stdio.h>extern int Add_(int a,int b);
myprint.c
?
#include "myprint.h"
void printf_(const char* str)
{printf("hello %s [%d]\n",str,(int)time(NULL));
}
myprint.h
#pragma once
#include <stdio.h>
#include <time.h>extern void printf_(const char* str);
如下:?
打包靜態庫的步驟
- 先將myadd.c和mysub.c?變成生成對應的二進制文件
-
使用ar?歸檔工具對兩個二進制文件進行打包,同時帶上選項**-rc**(r和c分別代表replace和creat),這里的庫名是hello。
?ar -rc libhello.a *.o
-
上面這兩個步驟其實就把靜態庫打包好了,下面我們還有做一個工作就是發布靜態庫,簡單地說,就是把頭文件和靜態庫組織起來,頭文件放在include?下,如下:
-
這樣一個庫文件就可以給別人使用了。
上面的所有步驟我們可以寫進Makefile里,利用make指令一鍵打包和make output發布,如下:libhello.a:myprint.o mymath.oar -rc libhello.a myprint.o mymath.o mymath.o:mymath.cgcc -c mymath.c -o mymath.o myprint.o:myprint.cgcc -c myprint.c -o myprint.o.PHONY:output output:mkdir -p output/lib mkdir -p output/includecp -rf *.h output/includecp -rf *.a output/lib .PHONY:clean clean:rm -rf *.o *.a output
2.2 靜態庫的使用
先把靜態庫放到一個測試目錄下:
然后編寫一段代碼:
?
#include "myprint.h"
#include "mymath.h"int main()
{int ret = Add_(1,2); printf("%d\n",ret);printf_("dgz");return 0;
}
編寫Makefile:
使用gcc編譯時,采用靜態鏈接編譯,所以要帶上選項**-static**,此外,因為我們使用了別人給的靜態庫,所以我們還有告訴編譯器庫文件所在路徑,頭文件所在路徑以及庫名,所以要用到以下三個選項:
- -L: 指明庫文件所在路徑
- -I: 指明頭文件所在路徑
- -l: 指明庫文件名稱,這里庫名就是hello(去掉前綴lib和后綴.a)
這里我們可以使用絕對路徑,使用下面的shell命令獲取當前所在路徑:
?
path=$(shell pwd)
Makefile編寫后如下:
path=$(shell pwd)mytest:test.c#-l 指定庫目錄名稱 -L 庫目錄路徑 -I 指定頭文件路徑gcc -o $@ $^ -I $(path)/output/include -L $(path)/output/lib -l hello -static
.PHONY:clean
clean:rm -f mytest
?
使用file指令查看鏈接屬性:
三、動態庫的打包與使用
3.1 動態庫的打包
我們同樣還是使用上面的那四個文件進行演示。
步驟:
- 先將mymath.c和myprint.c?變成生成對應的二進制文件。注意這里生成的二進制文件要帶上選項**-fPIC**,產生路徑無關碼,也就是這里使用相對地址,是動態確定的,不存在絕對地址
gcc -c -fPIC mymath.c -o mymath_d.o gcc -c -fPIC myprint.c -o myprint_d.o
- 使用gcc帶上選項**-shared** (生成共享的庫格式)對二進制文件進行打包
gcc -shared myprint_d.o mymath_d.o -o libhello.so
- 最后一步就是對動態庫進行發布,也就是將庫文件和頭文件進行組織打包
- 編寫Makefile:
libhello.so:myprint_d.o mymath_d.ogcc -shared myprint_d.o mymath_d.o -o libhello.so mymath_d.o:mymath.cgcc -c -fPIC mymath.c -o mymath_d.o myprint_d.o:myprint.cgcc -c -fPIC myprint.c -o myprint_d.o.PHONY:output output:mkdir -p output/lib mkdir -p output/includecp -rf *.h output/includecp -rf *.so output/lib .PHONY:clean clean:rm -rf *.o *.so output
3.2 動態庫的使用
先把動態庫放到測試目錄下:
然后編寫Makefile,和靜態庫的使用類似:
?
path=$(shell pwd)mytest_d:test.c#-l 指定庫目錄名稱 -L 庫目錄路徑 -I 指定頭文件路徑gcc -o $@ $^ -I $(path)/output/include -L $(path)/output/lib -l hello
.PHONY:clean
clean:rm -f mytest_d
?編譯程序:?如果此時直接對程序進行編譯,不會報錯,但是執行的時候會報錯,無法打開共享庫里面的文件
?因為這里是動態鏈接,不僅要讓編譯器動態庫的路徑,還要讓操作系統知道,所以這里我們需要導入一個環境變量LD_LIBRARY_PATH,如下(上面已經說過,這里重復一次)
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/dgz/linux/lesson26/lib/uselib/output/lib
?此時再執行程序就不會報錯了:
使用file指令查看程序的鏈接屬性:
使用ldd指令查看程序依賴的庫:?
3.3 運行動態庫的四種方法
- 將對應到.so和.h文件拷貝到/usr/lib64和/usr/include
- 系統在在搜索頭文件時會先在系統默認路徑下搜索(上面兩個路徑),如果沒找到,但是環境變量LD_LIBRARY_PATH設置了,也會在該環境變量下搜索,這種方法是內存級的,退出就沒有了。
- 修改配置文件,配置/etc/ld.so.conf.d/
路徑下原有的文件
我們想要配置它只需要在該路徑下創建一個.conf文件
然后將我們要配置的路徑寫進該文件最后執行一下
ldconfig
- 建立軟連接
?建立成功,然后就可以找到了。
四、總makefile
.PHONY:all
all:libhello.so libhello.alibhello.so:myprint_d.o mymath_d.ogcc -shared myprint_d.o mymath_d.o -o libhello.so
mymath_d.o:mymath.cgcc -c -fPIC mymath.c -o mymath_d.o
myprint_d.o:myprint.cgcc -c -fPIC myprint.c -o myprint_d.olibhello.a:myprint.o mymath.oar -rc libhello.a myprint.o mymath.o
mymath.o:mymath.cgcc -c mymath.c -o mymath.o
myprint.o:myprint.cgcc -c myprint.c -o myprint.o
# 發布
.PHONY:output
output:mkdir -p output/lib mkdir -p output/includecp -rf *.h output/includecp -rf *.a output/libcp -rf *.so output/lib
.PHONY:clean
clean:rm -rf *.o *.a *.so output