![[linux倉庫]程序員的自我修養(yǎng):徹底弄懂Linux下的動(dòng)靜態(tài)庫](http://pic.xiahunao.cn/yaotu/[linux倉庫]程序員的自我修養(yǎng):徹底弄懂Linux下的動(dòng)靜態(tài)庫)
各位看官好我是egoist2023Linux Linux is not Unix 今天來學(xué)習(xí)動(dòng)靜態(tài)庫的相關(guān)知識(shí)。 如果覺得這篇文章有幫助歡迎您一鍵三連分享給更多人哦目錄什么是庫制作庫源代碼.o文件靜態(tài)庫-l 庫名稱-L庫文件路徑-I搜索頭文件路徑重談庫的定義動(dòng)態(tài)庫/lb/libmyc.so軟鏈接配置LD_LIBRARY_PATH環(huán)境變量更改系統(tǒng)配置文件擴(kuò)展同時(shí)存在,動(dòng)態(tài)鏈接同時(shí)存在,靜態(tài)鏈接靜態(tài)庫,動(dòng)態(tài)鏈接動(dòng)態(tài)庫,靜態(tài)鏈接使用外部庫總結(jié)什么是庫庫是寫好的現(xiàn)有的成熟的可以復(fù)?的代碼?,F(xiàn)實(shí)中每個(gè)程序都要依賴很多基礎(chǔ)的底層庫不可能每個(gè)?的代碼都從零開始因此庫的存在意義非同尋常.動(dòng)靜態(tài)庫實(shí)際是Linux下指定目錄下的普通文件,可以被操作系統(tǒng)載?內(nèi)存執(zhí)?。靜態(tài)庫.a[Linux]、.lib[windows]動(dòng)態(tài)庫.so[Linux]、.dll[windows]我們把.o稱為可重定位目標(biāo)文件.那么什么叫可重定位呢?這里先拋出這個(gè)概念,留在之后解惑.制作庫在制作庫中圍繞開發(fā)者和使用者角度進(jìn)行展開:從庫的開發(fā)者來說,提供簡潔易用的接口避免過度設(shè)計(jì);從庫的使用者來說,必須是能夠簡單上手的,不需要自己多寫一份代碼.這里講一個(gè)小故事對制作庫進(jìn)行展開:大學(xué)中大部分都接觸過C語言課程設(shè)計(jì)報(bào)告,而張三(C語言學(xué)的很好)和李四是同班同學(xué)且也是關(guān)系很鐵的宿友.某天,他們的C語言老師王五要求他們提交課設(shè)代碼(完成兩個(gè)數(shù)字相加)給他看.作業(yè)一發(fā)布,張三很快就寫好了,并將頭源文件進(jìn)行了分離;但李四內(nèi)絲毫沒有頭緒,于是就想到了自己那個(gè)C語言考99分的宿友張三,跟他說:兄弟啊!這幾天王老師不是布置了個(gè)C語言作業(yè)嗎?但是我有些沒頭緒,可以借下你的代碼看看嗎?張三那肯定也是沒有猶豫的,于是他有三種方法可以提供給李四.源代碼張三回到自己的電腦桌前,直接將自己的源代碼拷給了李四,跟李四說:我的源代碼已經(jīng)發(fā)給你了,但是怕老師可能會(huì)發(fā)現(xiàn)是我給你的源代碼,你把里面的變量名改下,以防被發(fā)現(xiàn).最后執(zhí)行這條指令就可以跑通程序.gcc -o myexe *.c.o文件但是這個(gè)老師可不簡單,如果給我發(fā)現(xiàn)你們的代碼實(shí)現(xiàn)邏輯是類似的,直接給你們扣學(xué)分.這時(shí)張三就慌了啊!跟李四說:這老師賊心挺重,沒辦法兄弟,要不這樣我只給你提供.o和.h文件,并按照我發(fā)給你的指令給他看你的實(shí)現(xiàn)結(jié)果就好.張三自己的.c文件編譯形成了.o文件和.h發(fā)給了李四.gcc -o myexe *.o靜態(tài)庫突然王五老師又布置了個(gè)作業(yè),這次作業(yè)是模擬實(shí)現(xiàn)庫文件的打開、寫入、關(guān)閉,并且不要讓我發(fā)現(xiàn)你們的測試用例也是一樣的!!!張三也是很快的寫好了,但里面含有十多個(gè).c文件.此時(shí)李四又來找張三要了,張三依舊怕自己被老師發(fā)現(xiàn),依舊形成.o文件和.h文件一同發(fā)給了李四.但李四看到這么多.o文件就頭痛,跟張三說有沒有簡單的方法呢?可以變成一個(gè)簡單上手的文件嗎?這時(shí)張三又在冥思苦想:把多個(gè).o文件封裝成一個(gè),同時(shí)又要不把自己的實(shí)現(xiàn)方法暴露出來?把多個(gè).o進(jìn)行打包成靜態(tài)庫啊!,告訴李四自己需要調(diào)用哪些方法就行了.靜態(tài)庫的本質(zhì):庫源代碼形成的obj進(jìn)行打包.如何進(jìn)行打包呢?ar命令專門用來形成靜態(tài)庫的命令.是gnu歸檔?具:rc表示(replace and create)t:列出靜態(tài)庫中的?件v:verbose詳細(xì)信息張三把自己的靜態(tài)庫.a文件和.h文件一同發(fā)給了李四.-l(小L) 庫名稱此時(shí)李四調(diào)用了方法后,用gcc命令鏈接形成myexe可執(zhí)行程序時(shí)卻出現(xiàn)了問題:沒有找到這些函數(shù)的定義?于是張三指出沒有指定庫名稱.那該如何指定庫名稱呢?用 -l 庫名稱(去掉lib和后綴.a) 指定庫名稱果然成了,但是李四說為什么我平常使用C庫時(shí)不用指定庫名稱呢?不用帶-lc呢?張三說道:你以為gcc是干啥的?專門用來編譯c語言的默認(rèn)就認(rèn)識(shí)!!!輸出結(jié)論:如果使用任何第三方庫至少需要使用-I表明庫名稱-L(大L) 庫文件路徑但是我們依舊可以看到指定庫名稱后還是不能形成可執(zhí)行程序,報(bào)錯(cuò)顯示該沒有找到該庫路徑.使用 -L 路徑 來指明路徑:形成了可執(zhí)行程序!!!于是李四又問:為什么C庫不需要指明路徑呢?張三說到:這是因?yàn)槲覀冏约旱膸鞗]有在系統(tǒng)的默認(rèn)路徑下.gcc/g編譯器找?guī)煲话銜?huì)在默認(rèn)路徑下查找!輸出結(jié)論:因此所謂的庫的安裝就是把庫文件拷貝到系統(tǒng)默認(rèn)路徑下/lib64那么如果我們把自己的靜態(tài)庫拷貝到默認(rèn)路徑下是不是就可以不用指定路徑:此時(shí)再次進(jìn)行鏈接總算形成了可執(zhí)行程序:最佳實(shí)踐:gcc -o myexe 庫 -L指明路徑 -l庫名稱我們之前說過如果是包自己的頭文件是使用 ,而不能像C庫一樣使用 ,那如果也想像庫一樣使用 呢?指定一個(gè)搜索路徑表明頭文件搜索路徑,include但這樣還不夠方便,可以不指名.h路徑嗎?我們知道C頭文件肯定放在某個(gè)特定文件中:那么把我們自己的頭文件放到這里面應(yīng)該也是可以的:-I 搜索頭文件路徑靜態(tài)庫.a程序在編譯鏈接的時(shí)候把庫的代碼鏈接到可執(zhí)??件中程序運(yùn)?的時(shí)候?qū)⒉辉傩枰o態(tài)庫。?個(gè)可執(zhí)行程序可能?到許多的庫這些庫運(yùn)?有的是靜態(tài)庫有的是動(dòng)態(tài)庫?我們的編譯默認(rèn)為動(dòng)態(tài)鏈接庫只有在該庫下找不到動(dòng)態(tài).so的時(shí)候才會(huì)采?同名靜態(tài)庫。我們也可以使? gcc的 -static強(qiáng)轉(zhuǎn)設(shè)置鏈接靜態(tài)庫。這個(gè)命令用來查看自己以來的動(dòng)態(tài)庫:可以看見我們的libmyc.a沒有在里面,即查不到靜態(tài)庫的依賴,因?yàn)殪o態(tài)庫把自己的代碼合并到里面了.如何證明呢?既然可以將庫是靜態(tài)連接的,那么C庫也是同樣可以的:-static表明是靜態(tài)連接.再次ldd查看該可執(zhí)行程序依賴的動(dòng)態(tài)庫,這時(shí)就沒有了.但是內(nèi),此時(shí)王老師又布置了學(xué)生管理系統(tǒng)的作業(yè),而此時(shí)張三又要重復(fù)一次把自己的.a文件和.h文件拷貝一份給李四,太麻煩了吧?這時(shí)之前學(xué)過的make/Makefile就有了大作用!!!make:將多個(gè).c文件編譯形成.o文件,再將.o文件通過ar指令打包成一個(gè)靜態(tài)庫libmy.cmake output:新建目錄mylibc、mylibc/include和mylibc/lib,拷貝.h和.a文件分別到mylibc/include和mylibc/lib,對mylibc目錄下的文件進(jìn)行打包為.tgz壓縮文件make clean: 刪除以.a .o為后綴、mylibc目錄和mylibc .tgz壓縮文件libmyc.a:mymath.o mystdio.o ar -rc $ $^ %.o:%.c gcc -c $ .PHONY:clean clean: rm -rf *.a *.o mylibc *.tgz .PHONY:output output: mkdir mylibc mkdir -p mylibc/include mkdir -p mylibc/lib cp *.h mylibc/include cp *.a mylibc/lib tar czf mylibc.tgz mylibc于是張三通過make、make output生成了mylibc.tgz壓縮文件,把這個(gè)壓縮文件拷貝給李四,跟李四說你按照我給你的指令進(jìn)行解壓縮,再按照指定頭文件路徑、庫名稱和指定庫路徑的方式進(jìn)行鏈接成可執(zhí)行程序就可以了.進(jìn)行解包:gcc指定頭文件路徑庫文件路徑庫名稱:重談庫的定義什么是庫?庫頭文件 庫文件庫使用需要搜索 1.頭文件(-1,大i) 2.庫路徑(-L) 3.庫是誰(-1小I)庫 如果 不想過多使用上面的選項(xiàng)則搬到系統(tǒng)特定路徑庫安裝: 本質(zhì)是把頭文件(/usr/include) 和庫文件(/ib64)拷貝到系統(tǒng)的指定的、默認(rèn)的路徑下編譯器能找到的路徑下!!!動(dòng)態(tài)庫動(dòng)態(tài)庫.so程序在運(yùn)?的時(shí)候才去鏈接動(dòng)態(tài)庫的代碼多個(gè)程序共享使用庫的代碼。?個(gè)與動(dòng)態(tài)庫鏈接的可執(zhí)??件僅僅包含它?到的函數(shù)??地址的?個(gè)表?不是外部函數(shù)所在?標(biāo)?件的整個(gè)機(jī)器碼在可執(zhí)行?件開始運(yùn)?以前外部函數(shù)的機(jī)器碼由操作系統(tǒng)從磁盤上的該動(dòng)態(tài)庫中復(fù)制到內(nèi)存中這個(gè)過程稱為動(dòng)態(tài)鏈接dynamic linking動(dòng)態(tài)庫可以在多個(gè)程序間共享所以動(dòng)態(tài)鏈接使得可執(zhí)??件更?節(jié)省了磁盤空間。操作系統(tǒng)采?虛擬內(nèi)存機(jī)制允許物理內(nèi)存中的?份動(dòng)態(tài)庫被要?到該庫的所有進(jìn)程共?節(jié)省了內(nèi)存和磁盤空間。gcc -fPIC -c *.c 把.c文件形成.o文件gcc -o libmyc.so *.o -shared 將.o文件進(jìn)行動(dòng)態(tài)打包shared: 表??成共享庫格式fPIC產(chǎn)?位置?關(guān)碼(position independent code)庫名規(guī)則libxxx.soMake/Makefile方法對.tgz壓縮文件進(jìn)行解包:libmyc.so:mymath.o mystdio.o gcc -o $ $^ -shared %.o:%.c gcc -fPIC -c $ .PHONY:clean clean: rm -rf *.so *.o mylibc *.tgz .PHONY:output output: mkdir mylibc mkdir -p mylibc/include mkdir -p mylibc/lib cp *.h mylibc/include cp *.so mylibc/lib tar czf mylibc.tgz mylibc當(dāng)我們執(zhí)行形成的可執(zhí)行程序時(shí):卻報(bào)錯(cuò)了,這是為什么呢?根據(jù)前面所學(xué)我不是已經(jīng)告訴系統(tǒng)或加載器,我的庫在哪個(gè)路徑下了么?你睜眼就報(bào)個(gè)錯(cuò)?這是因?yàn)槟闵鲜霾僮魇侵桓嬖V了編譯器,什么意思呢?編譯器如 gcc在編譯時(shí)只會(huì)把“需要鏈接哪些庫”的信息寫進(jìn)可執(zhí)行文件比如 a.out不會(huì)把庫本身打包進(jìn)去。(而我們靜態(tài)庫是把.o文件融進(jìn)可執(zhí)行程序里了,靜態(tài)庫刪掉程序照樣可以運(yùn)行)此時(shí)形成了可執(zhí)行程序,但是這個(gè)程序運(yùn)行時(shí),是由OS的動(dòng)態(tài)鏈接器如 ld-linux-x86-64.so.2去查找這些庫,已經(jīng)跟編譯器沒有關(guān)系了!!!/lb/libmyc.so那為什么只有我們自己的庫OS動(dòng)態(tài)鏈接器會(huì)找不到?而C語言的卻可以,毫無疑問C肯定也是放在了某個(gè)特定目錄下:運(yùn)行的時(shí)候查找動(dòng)態(tài)庫加載器或者0S可以直接去默認(rèn)路徑下查找/lib那么把我們自己的.so拷貝到Iib就行了,但是這種方式極其不優(yōu)雅,有時(shí)還是有風(fēng)險(xiǎn)的!軟鏈接軟連接的方式鏈接/lb/libmyc.so:配置LD_LIBRARY_PATH環(huán)境變量導(dǎo)入環(huán)境變量:export LD_LIBRARY_PATH$LD_LIBRARY_PATH: /home/egoist/note/note26/other/mylibc/ lib/更改系統(tǒng)配置文件動(dòng)態(tài)庫查找路徑配置文件一般放在:/etc/ld.so.conf.d/里,將自己的庫配置進(jìn)去.執(zhí)行l(wèi)dconfig重新加載庫搜索路徑此時(shí)再執(zhí)行發(fā)現(xiàn)可以運(yùn)行了:擴(kuò)展同時(shí)存在,動(dòng)態(tài)鏈接動(dòng)態(tài)和靜態(tài)庫同時(shí)存在的時(shí)候gcc/g默認(rèn)優(yōu)先使用動(dòng)態(tài)庫默認(rèn)進(jìn)行動(dòng)態(tài)鏈接!同時(shí)存在,靜態(tài)鏈接-static:要求我們必須采用靜態(tài)鏈接的方案靜態(tài)庫,動(dòng)態(tài)鏈接一個(gè)可執(zhí)行程序可能會(huì)依賴多個(gè)庫如果我們只提供靜態(tài)庫即使我們是動(dòng)態(tài)鏈接gcc也沒有辦法只能對只提供靜態(tài)庫的庫進(jìn)行靜態(tài)鏈接動(dòng)態(tài)庫,靜態(tài)鏈接使用-static時(shí)要求靜態(tài)庫必須存在我們的指令是用C寫的,那么也是依賴于C語言庫的:使用外部庫現(xiàn)在沒接觸過太多的庫唯?接觸過的就是C、C標(biāo)準(zhǔn)庫這?推薦?個(gè)好玩的圖形庫ncurses// 安裝 // Centos $ sudo yum install -y ncurses-devel // ubuntu $ sudo apt install -y libncurses-dev系統(tǒng)中其實(shí)有很多庫它們通常由?組互相關(guān)聯(lián)的?來完成某項(xiàng)常??作的函數(shù)構(gòu)成。?如?來處理屏幕顯示情況的函數(shù)ncurses庫#include stdio.h #include string.h #include ncurses.h #include unistd.h #define PROGRESS_BAR_WIDTH 30 #define BORDER_PADDING 2 #define WINDOW_WIDTH (PROGRESS_BAR_WIDTH 2 * BORDER_PADDING 2) // 加邊框的寬度 #define WINDOW_HEIGHT 5 #define PROGRESS_INCREMENT 3 #define DELAY 300000 // 微秒300毫秒 int main() { initscr(); start_color(); init_pair(1, COLOR_GREEN, COLOR_BLACK); // 已完成部分綠?前景??背景 init_pair(2, COLOR_RED, COLOR_BLACK); // 剩余部分雖然?紅?可能不太合適但為演示目的紅色背景 cbreak(); noecho(); curs_set(FALSE); int max_y, max_x; getmaxyx(stdscr, max_y, max_x); int start_y (max_y - WINDOW_HEIGHT) / 2; int start_x (max_x - WINDOW_WIDTH) / 2; WINDOW *win newwin(WINDOW_HEIGHT, WINDOW_WIDTH, start_y, start_x); box(win, 0, 0); // 加邊框 wrefresh(win); int progress 0; int max_progress PROGRESS_BAR_WIDTH; while (progress max_progress) { werase(win); // 清除窗?內(nèi)容 // 計(jì)算已完成的進(jìn)度和剩余的進(jìn)度 int completed progress; int remaining max_progress - progress; // 顯?進(jìn)度條 int bar_x BORDER_PADDING 1; // 進(jìn)度條在窗?中的x坐標(biāo) int bar_y 1; // 進(jìn)度條在窗?中的y坐標(biāo)居中 // 已完成部分 attron(COLOR_PAIR(1)); for (int i 0; i completed; i) { mvwprintw(win, bar_y, bar_x i, #); } attroff(COLOR_PAIR(1)); // 剩余部分?背景?填充 attron(A_BOLD | COLOR_PAIR(2)); // 加粗并設(shè)置背景?為紅?僅?于演? for (int i completed; i max_progress; i) { mvwprintw(win, bar_y, bar_x i, ); } attroff(A_BOLD | COLOR_PAIR(2)); // 顯?百分? char percent_str[10]; snprintf(percent_str, sizeof(percent_str), %d%%, (progress * 100) / max_progress); int percent_x (WINDOW_WIDTH - strlen(percent_str)) / 2; // 居中顯? mvwprintw(win, WINDOW_HEIGHT - 1, percent_x, percent_str); wrefresh(win); // 刷新窗?以顯?更新 // 增加進(jìn)度 progress PROGRESS_INCREMENT; // 延遲?段時(shí)間 usleep(DELAY); } // 清理并退出ncurses模式 delwin(win); endwin(); return 0; }總結(jié)本文介紹了Linux系統(tǒng)中的動(dòng)靜態(tài)庫概念及使用方法。從開發(fā)者角度講解了如何制作靜態(tài)庫(.a)和動(dòng)態(tài)庫(.so)包括ar命令打包、gcc編譯選項(xiàng)等關(guān)鍵技術(shù)。通過張三和李四的案例生動(dòng)演示了從源代碼到.o文件再到靜態(tài)庫的制作過程并詳細(xì)說明了-l、-L、-I等關(guān)鍵參數(shù)的使用場景。特別強(qiáng)調(diào)了動(dòng)態(tài)庫運(yùn)行時(shí)查找路徑的配置方法包括環(huán)境變量LD_LIBRARY_PATH和系統(tǒng)配置文件設(shè)置。文章還對比了動(dòng)靜態(tài)庫的特點(diǎn)指出默認(rèn)優(yōu)先使用動(dòng)態(tài)鏈接的規(guī)則并介紹了強(qiáng)制靜態(tài)鏈接的方法。最后以ncurses圖形庫為例展示了外部庫的實(shí)際應(yīng)用。