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