🐼故事背景
假設今天你有一位舍友。你需要幫助他完成老師的作業。而他寫的代碼依賴兩個文件(mymath.h,mystdio.h)。但是這兩個文件的功能他不會寫,他只會調用。他的調用代碼:
#include"mystdio.h"
#include"mymath.h"
#include<string.h>
#include<unistd.h>int main()
{MYFILE* fp = myfopen("log.txt","w");if(!fp){perror("myfp");return 1;}int cnt = 20;const char* s = "hello myfile";while(cnt--){myfwrite(s,strlen(s),fp);if(cnt == 5){myfflush(fp);}}myfclose(fp);printf("10+20 = %d\n",Add(10,20));return 0;
}
但是你會這兩個文件具體功能的實現啊。出于熱心腸的你現在要幫助他。你會怎么幫助他呢?
🐼靜態庫制作?
1??直接把源文件給他
處于熱心腸的你。將你寫好的代碼mystd.c, mymath.c, mystdio.h, mymath.h這四個文件都給了你舍友。你的舍友通過編譯鏈接完美應付了考試。你幫他順利過關了。
2??給他.o文件
我們知道。形成一個可執行程序最關鍵的步驟就是先把源文件編譯成.o文件。再把這些.o文件通過鏈接器鏈接成.exe文件。如圖:
你出于安全考慮,這次不給他源文件了。而是直接將鏈接好的.o文件交給他。并且給了他說明書(.h)文件
gcc -c *.c
他再將他的test.c編譯形成.o文件。和你給的.o大家再一鏈接又編譯形成了可執行程序。這次又順利過關了!
gcc -c test.c #-rw-rw-r-- 1 lsg lsg 59 Aug 3 18:09 mymath.c
#-rw-rw-r-- 1 lsg lsg 34 Aug 3 18:09 mymath.h
#-rw-rw-r-- 1 lsg lsg 1232 Aug 3 18:17 mymath.o
#-rw-rw-r-- 1 lsg lsg 2098 Aug 3 18:09 mystdio.c
#-rw-rw-r-- 1 lsg lsg 560 Aug 3 18:09 mystdio.h
#-rw-rw-r-- 1 lsg lsg 3336 Aug 3 18:17 mystdio.o
#-rw-rw-r-- 1 lsg lsg 463 Aug 3 17:58 test.c
#-rw-rw-r-- 1 lsg lsg 2200 Aug 3 18:15 test.ogcc -o myexe *.o
3??將.o文件打包形成靜態庫
出于安全考慮。連.o都不想給他了。于是你把你編譯好.o打包形成一個靜態庫。交給了你的舍友并給了他說明書。
為什么能這么做呢?因為你心里清楚。靜態庫文件的本質就是將.obj進行打包。他的文件最后也要變成.o的。所有.o的文件鏈接就成了可執行程序。
你:
# 編譯將所有.c形成.o
gcc -c *.c # 將所有.o 文件打包成靜態庫
ar -rc libmyc.a *.o// 將形成好的libmyc.a交給你的舍友并且把說明書.h文件也給他。
你的舍友:
#直接編譯鏈接
gcc -o myexe test.c # error
?發現報錯了!為什么呢?根據報錯信息。編譯器不認識我給他提供的函數。于是他上網查閱。
發現需要帶-l 標明要連接哪個庫。因為庫多了
為什么要帶-l啊。為什么鏈接C語言不需要帶-lc
因為gcc就是編譯C語言的。默認就要認識。
而我們如果使用任何第三方庫。至少要使用-l表明庫名稱,指明你要鏈接誰
# 表明要連接mylibc庫
gcc -o myexe test.c -lmyc
# 注意 lib 和.a不需要帶
但是發現還是不行根據報錯原因。原來是庫路徑找不到(編譯器不認識)。
通過-L指定你要鏈接庫的位置(搜索目錄)
gcc -o myexe test.c -L. -lmyc
鏈接成功!
但是你的舍友想把#include"mystdio.h"? ?#include"mymath.h"換成#include <mystdio.h>
#include <mymath.h> 可以嗎?
通過-I指定一個搜索路徑。表明頭文件搜索路徑。這樣編譯器默認從指定的路徑下查找依賴頭文件
gcc -o myexe test.c -L. -lmyc -I.
通過上述做法。你的舍友就成功的使用了你給他提供的靜態庫!?
?但是當你舍友查看庫所依賴的文件時。發現并沒有查到libmyc.a,這是為什么??
ldd myexelinux-vdso.so.1 (0x00007ffc62cb8000)libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f0a80d2d000)/lib64/ld-linux-x86-64.so.2 (0x00007f0a80f64000)
因為鏈接的是靜態庫。靜態庫本質是.o文件的集合。一旦鏈接,形成.exe,就不再依賴靜態庫了。因為靜態庫早已合并自已的代碼到可執行程序中了
?現在還有一個問題。就是為什么我們鏈接其他庫時(比如pthread庫)。并不需要帶-L -I 這樣的選項。
其實當庫沒有在系統默認的路徑下時。gcc/g++編譯器找庫。默認會在系統默認路徑下查找,而所謂庫的安裝。就是把庫文件拷貝到系統默認路徑下。
這里以ubuntu為例。我的庫文件系統默認路徑是/lib/x86_64-linux-gnu/ (怎么找的,ldd 查看/usr/bin/ls依賴的系統庫目錄即可)
這是不是意味著。只要我們把庫文件拷貝到/lib/x86_64-linux-gnu/。就不用帶-L選項。編譯器能根據系統指定路徑來找到我們的庫目錄。
并且只要我們把頭文件拷貝到/usr/include/?。就不用帶-I選項。編譯器能根據系統指定路徑來找到我們的源文件依賴的頭文件目錄。
# 拷貝庫文件到系統指定目錄下 --->省去了 -L
sudo cp *.a /lib/x86_64-linux-gnu/# 拷貝頭文件到系統執行目錄下 ---> 省去了 -I
sudo cp *.h /usr/include/# 直接連接我們的庫即可 -l必須帶!!!(因為是第三方庫)
gcc -o myexe test.c -lmyc
所以庫安裝的本質就是將頭文件和庫文件根據特定目錄結構組織好拷貝到系統指定目錄下,編譯器默認能夠找到的目錄下!
??最佳實踐
如果我們想批量化的向別人提供我們寫好的庫,并以庫文件,頭文件目錄形式打包給給人使用。可以借助Makefile:
開發者:
# 將所有.o打包形成 .a
libmyc.a: mystdio.o mymath.oar -rc $@ $^# 編譯并將.c->.o
%.o:%.cgcc -c $<.PHONY:clean
clean: rm -rf *.a *.o output *.tgz# 發布
.PHONY:output
output:mkdir -p outputmkdir -p output/lib/mkdir -p output/include/cp *.h output/includecp *.a output/lib/tar -czf mylib.tgz output
使用者:?
tar -xzf mylib.tgz
# 最終將目錄樹長這樣
#tree output
#output
#├── include
#│?? ├── mymath.h
#│?? └── mystdio.h
#└── lib
# └── libmyc.agcc -o myexe test.c -I./output/include/ -L./output/lib -lmyc
如果嫌麻煩。開發者也可以在安裝時。自帶一個腳本幫使用者把庫文件和頭文件。拷貝到系統指定目錄下。這樣就不需要使用者指定頭文件位置和庫文件位置了
🐼動態庫制作
跟形成靜態庫原理類似。
第一步。將源文件.c編譯形成.o文件。注意。在編譯形成.o文件時。要帶上-fPIC(與位置無關碼)
# .c -> .o
gcc -fPIC -c *.c
將.o打包形成動態庫
gcc -o libmyc.so *.o -shared
編譯并鏈接動態庫
gcc -o myexe test.c -lmyc -L. -I.
運行程序。./myexe
結果報錯了!why???
#./myexe
#./myexe: error while loading shared libraries: libmyc.so: cannot open shared object file: #No such file or directory#lsg@hcss-ecs-0228:~/code/code/25_8_3/person$ ldd myexe
# linux-vdso.so.1 (0x00007ffd9d53f000)
# libmyc.so => not found
# libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fbb1946d000)
# /lib64/ld-linux-x86-64.so.2 (0x00007fbb196a4000)
我們不是已經告訴了系統。庫在哪個路徑下嗎。為什么還是會報錯呢??并且在鏈接這個庫時。系統找不到。這是為什么?
因為我們只告訴了編譯器。我們的動態庫在哪。而動態庫是運行時才會加載到內存中。而此時已經和編譯器無關了。所以結論就是在加載可執行程序的時候,也要找到所依賴的庫。換句話說。為了運行時,系統(加載器)也能找到動態庫,我們需要將庫顯示拷貝到系統指定目錄下。當然,這只是方法之一。只要保證系統(加載器)在加載動態庫時也能找到動態庫即可。
sudo cp libmyc.so /lib/x86_64-linux-gnu/ #ldd myexe
# linux-vdso.so.1 (0x00007ffc7834c000)
# libmyc.so => /lib/x86_64-linux-gnu/libmyc.so (0x00007f2c3c987000)
# libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f2c3c75e000)
# /lib64/ld-linux-x86-64.so.2 (0x00007f2c3c99a000)
我們也可以通過軟鏈接的方式關聯將庫文件的安裝目錄進行關聯?
我們將動態庫也以目錄形式打包發布出去:
如果我們自已使用(軟連接可以根據你的絕對路徑修改,這里小編就不改了)
libmyc.so:mystdio.o mymath.ogcc -o $@ $^ -shared%.o:%.cgcc -fPIC -c $<.PHONY:clean
clean:rm -rf *.o *.so sudo unlink /lib/x86_64-linux-gnu/libmyc.so sudo unlink /usr/include/mymath.h sudo unlink /usr/include/mystdio.h.PHONY:self
self:sudo ln -s /home/lsg/code/code/25_8_3/mylib/libmyc.so /lib/x86_64-linux-gnu/libmyc.sosudo ln -s /home/lsg/code/code/25_8_3/mylib/mymath.h /usr/include/mymath.hsudo ln -s /home/lsg/code/code/25_8_3/mylib/mystdio.h /usr/include/mystdio.h// 自已寫的庫完全可以拷貝到系統指定目錄下。就不用軟連接了(軟連接只能在你指定的路徑下使用你的庫)
??最佳實踐
如果我們自已想用自已寫好的庫,直接拷貝到系統指定目錄下!
libmyc.so:mystdio.o mymath.ogcc -o $@ $^ -shared%.o:%.cgcc -fPIC -c $<.PHONY:clean
clean:rm -rf *.o *.so sudo rm /lib/x86_64-linux-gnu/libmyc.so sudo rm /usr/include/mymath.h mystdio.h .PHONY:copy
copy:sudo cp mymath.h mystdio.h /usr/include/sudo cp libmyc.so /lib/x86_64-linux-gnu/
發布給別人使用
libmyc.so:mystdio.o mymath.ogcc -o $@ $^ -shared%.o:%.cgcc -fPIC -c $<.PHONY:clean
clean:rm -rf output *.o *.so *.tgz.PHONY:output
output:mkdir -p output mkdir -p output/lib/mkdir -p output/include/cp *.h output/include/cp *.so output/lib/tar -czf mylib.tgz output
當然。運行時找到動態庫的原理還有幾種。比如配置?LD_LIBRARY_PATH環境變量。
更改系統配置文件。將動態庫,查找路徑,使其全局有效。
還需要注意的是:
?動態庫和靜態庫同時存在的時候,gcc/g++優先使用動態庫。默認進行動態鏈接!
?一個可執行程序。可能依賴多個庫,但是如果我們只提供靜態庫,即使動態鏈接。gcc也沒辦法,只能對只提供的靜態的庫,進行靜態鏈接!
?如果我們顯示帶上-static選項。那么我們就一定要提供靜態庫了,否則,會報錯!?