GCC?指令詳解及動(dòng)態(tài)庫(kù)、靜態(tài)庫(kù)的使用方法
一、GCC
1.1 GCC 介紹
GCC 是 Linux 下的編譯工具集,是「GNU Compiler Collection」的縮寫,包含 gcc、g++ 等編譯器。這個(gè)工具集不僅包含編譯器,還包含其他工具集,例如 ar、nm 等。
GCC 工具集不僅能編譯 C/C++ 語(yǔ)言,其他例如 Objective-C、Pascal、Fortran、Java、Ada 等語(yǔ)言均能進(jìn)行編譯。GCC 還可以根據(jù)不同的硬件平臺(tái)進(jìn)行編譯,即能進(jìn)行交叉編譯,在 A 平臺(tái)上編譯 B 平臺(tái)的程序,支持常見的 X86、ARM、PowerPC、mips 等,以及 Linux、Windows 等軟件平臺(tái)。
1.2 安裝 GCC
首先,查看 gcc 是否安裝:
# 查看 gcc 版本 $ gcc -v $ gcc --version # 查看 g++ 版本 $ g++ -v $ g++ --version
如果在輸入指令后可以獲取到 gcc 版本,那么就表明你的 Linux 中已經(jīng)安裝了 gcc:
如果沒有安裝,則可按照如下方法安裝 gcc:
# centos $ sudo yum update # 更新本地的軟件下載列表, 得到最新的下載地址 $ sudo yum install gcc g++ # 通過下載列表中提供的地址下載安裝包, 并安裝
1.3 GCC 工作流程
1.3.1 一般使用流程
首先準(zhǔn)備一個(gè) C 語(yǔ)言代碼,并命名為 test.c:
#include <stdio.h> #define MAX 3 int main() { int i; for (i = 1; i <= MAX; i++) { printf("Hello World\n"); // 輸出 Hello World } return 0; }
一般情況下,我們可以直接通過 $ gcc test.c -o test
編譯 test.c,并通過$ ./test
指令運(yùn)行生成的可執(zhí)行文件:
-o
:output,是 gcc 編譯器的可選參數(shù),用于指定輸出文件名及路徑,默認(rèn)輸出到當(dāng)前路徑下。下圖展示了如何通過 -o 參數(shù)修改輸出路徑:
或者不使用 -o 參數(shù),則生成一個(gè)默認(rèn)名稱的可執(zhí)行文件 a.out:
實(shí)際上,GCC 編譯器在對(duì)程序進(jìn)行編譯的時(shí)候,分為了四個(gè)步驟:
預(yù)處理(Pre-Processing):
- 在這個(gè)階段主要做了三件事:展開頭文件 、宏替換 、去掉注釋行
- 結(jié)果得到的還是一個(gè) C 程序,通常是以 .i 作為文件擴(kuò)展名
編譯(Compiling) :
- 在這個(gè)階段中,gcc 首先要檢查代碼的規(guī)范性、是否有語(yǔ)法錯(cuò)誤等,以確定代碼實(shí)際要做的工作
- 在檢查無誤后,gcc 把代碼編譯成匯編代碼,得到一個(gè)以 .s 作為文件拓展名的匯編文件。
匯編(Assembling):
+ 匯編階段是把編譯階段生成的 .s 文件轉(zhuǎn)化成目標(biāo)文件 + 最終得到一個(gè)以 .o 結(jié)尾的二進(jìn)制文件
鏈接(Linking):這個(gè)階段需要 GCC 調(diào)用鏈接器對(duì)程序需要調(diào)用的庫(kù)進(jìn)行鏈接,最終得到一個(gè)可執(zhí)行的二進(jìn)制文件
而 GCC 的編譯器可以將這 4 個(gè)步驟合并成一個(gè),這也就是為什么我們使用$ gcc test.c -o test
就可以直接生成可執(zhí)行文件 test 的原因。下面我們對(duì)這 4 個(gè)步驟做個(gè)詳細(xì)的介紹。
1.3.2 詳細(xì)的工作流程
1.3.2.1 預(yù)處理
# 通過添加參數(shù) -E 生成預(yù)處理后的 C 文件 test.i # 必須通過 -o 參數(shù)指定輸出的文件名 $ gcc -E test.c -o test.i
讓我們來觀察一下 test.i 中的代碼內(nèi)容(太長(zhǎng)了,只觀察 main 函數(shù)中的替換情況):
int main() { int i; for (i = 1; i <= 3; i++) { printf("Hello World\n"); } return 0; }
通過分析 test.i 可以發(fā)現(xiàn):
- 宏定義 MAX 被替換為了相應(yīng)的值 3
- 注釋「// 輸出 Hello World」也被去掉了
1.3.2.2 編譯
# 通過添加參數(shù) -S 將 test.i 轉(zhuǎn)換為匯編文件 test.s(默認(rèn)生成 .s 文件) $ gcc -S test.i $ gcc -S test.i -o test.s # 寫法二
1.3.2.3 匯編
# 通過匯編得到二進(jìn)制文件 test.o(默認(rèn)生成 .o 文件,object) $ gcc -c test.s $ gcc -c test.s -o test.o # 寫法二
1.3.2.4 鏈接
# 通過鏈接得到可執(zhí)行文件 test $ gcc test.o -o test
在成功生成 test.o 文件后,就進(jìn)入了鏈接階段。在這里涉及到一個(gè)重要的概念:函數(shù)庫(kù)。
在 test.c 的代碼中,我們通過print()
函數(shù)打印 Hello World 語(yǔ)句;但是在這段程序中并沒有定義 printf 的函數(shù)實(shí)現(xiàn),且在預(yù)編譯中包含進(jìn)去的「stdio.h」中也只有該函數(shù)的聲明extern int printf (const char *__restrict __format, ...);
,而沒有定義函數(shù)的實(shí)現(xiàn),那么是在哪里實(shí)現(xiàn)的呢?
答案就是:系統(tǒng)把這些函數(shù)實(shí)現(xiàn)都做到了名為 libc.so.6 的庫(kù)文件中去了,在沒有特別指定時(shí),gcc 會(huì)到系統(tǒng)默認(rèn)的搜索路徑 /usr/lib64 下進(jìn)行查找,也就是鏈接到 libc.so.6 庫(kù)函數(shù)中去,這樣就有函數(shù) printf 的實(shí)現(xiàn)了,而這也就是鏈接的作用。
而函數(shù)庫(kù)一般分為靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù)兩種:
- 靜態(tài)庫(kù)是指在編譯鏈接時(shí),把庫(kù)文件的代碼全部加入到可執(zhí)行文件中,因此生成的文件比較大,但在運(yùn)行時(shí)也就不需要庫(kù)文件了。在 Linux 中靜態(tài)庫(kù)一般以 .a 作為后綴。
- 動(dòng)態(tài)庫(kù)與之相反,在編譯鏈接時(shí)并沒有把庫(kù)文件的代碼加入到可執(zhí)行文件中,而是在程序執(zhí)行時(shí)鏈接文件加載庫(kù),這樣就可以節(jié)省系統(tǒng)的開銷。在 Linux 中動(dòng)態(tài)庫(kù)一般以 .so 作為后綴。
如前面所述的 libc.so.6 就是動(dòng)態(tài)庫(kù),gcc 在編譯時(shí)默認(rèn)使用動(dòng)態(tài)庫(kù)。完成了鏈接之后,gcc 就可以生成可執(zhí)行文件了。
有關(guān)動(dòng)態(tài)庫(kù)和靜態(tài)庫(kù)的詳細(xì)介紹,將在下文進(jìn)行具體講解。
1.3.2.5 總結(jié)
最后,通過一張圖來總結(jié)一下上述流程:
在 Linux 下使用 GCC 編譯器編譯單個(gè)文件十分簡(jiǎn)單,直接使用
$ gcc test.c
(test.c 為要編譯的 C 語(yǔ)言的源文件),GCC 會(huì)自動(dòng)生成文件名為 a.out 的可執(zhí)行文件(也可以通過參數(shù) -o 指定生成的文件名);也就是通過一個(gè)簡(jiǎn)單的命令就可以將上邊提到的 4 個(gè)步驟全部執(zhí)行完畢了;但是如果想要單步執(zhí)行也是沒問題的。
1.4 GCC 常用參數(shù)
下面的表格中列出了一些常用的 gcc 參數(shù),這些參數(shù)在 gcc 命令中沒有位置要求,只需要編譯程序的時(shí)候?qū)⑿枰膮?shù)指定出來即可。
gcc 編譯選項(xiàng) | 解釋說明 |
---|---|
-E | 預(yù)處理,主要是進(jìn)行宏展開等步驟,生成 test.i |
-S | 編譯指定的源文件,但是不進(jìn)行匯編,生成 test.s |
-c | 編譯、匯編源文件,但是不進(jìn)行鏈接,生成 test.o |
-o | 指定鏈接的文件名及路徑 |
-g | 在編譯的時(shí)候,生成調(diào)試信息,該程序可以被調(diào)試器調(diào)試 |
-D | 在程序編譯的時(shí)候,指定一個(gè)宏 |
-std | 指定 C 方言,如 -std=c99。gcc 默認(rèn)的方言是 GNU C |
-l | 在程序編譯的時(shí)候,指定使用的庫(kù)(庫(kù)的名字一定要掐頭去尾,如 libtest.so 變?yōu)?test) |
-L | 在程序編譯的時(shí)候,指定使用的庫(kù)的路徑 |
-fpic | 生成與位置無關(guān)的代碼 |
-shared | 生成共享目標(biāo)文件,通常用在建立動(dòng)態(tài)庫(kù)時(shí) |
1.4.1 指定一個(gè)宏(-D)
在程序中我們可以通過使用#define
定義一個(gè)宏,也可以通過宏控制某段代碼是否能夠被執(zhí)行。
#include <stdio.h> int main() { int num = 60; printf("num = %d\n", num); #ifdef DEBUG printf("定義了 DEBUG 宏, num++\n"); num++; #else printf("未定義 DEBUG 宏, num--\n"); num--; #endif printf("num = %d\n", num); return 0; }
由于我們?cè)诔绦蛑胁]有定義 DEBUG 宏,所以第 8~9 行的代碼就不會(huì)被執(zhí)行:
那么如何才能夠在程序中不定義 DEBUG 宏的情況下執(zhí)行第 8~9 行的代碼呢?答案是通過 -D 參數(shù):
需要注意的是,-D 參數(shù)必須在生成 test.o 前使用(鏈接前)。如下所示,是無效的:
說了這么多,-D 參數(shù)有什么用呢?下面我們簡(jiǎn)單敘述一下 -D 參數(shù)的應(yīng)用場(chǎng)景。
1.4.1.1 應(yīng)用場(chǎng)景一
在發(fā)布程序的時(shí)候,一般都會(huì)要求將程序中所有的 log 輸出去掉,如果不去掉會(huì)影響程序的執(zhí)行效率,很顯然刪除這些打印 log 的源代碼是一件很麻煩的事情,解決方案是這樣的:
- 將所有的打印 log 的代碼都寫到一個(gè)宏判定中,可以模仿上邊的例子;
- 在調(diào)試程序的時(shí)候指定 -D,就會(huì)有 log 輸出;
- 在發(fā)布程序的時(shí)候不指定 -D,log 就不會(huì)輸出;
1.4.1.2 應(yīng)用場(chǎng)景二
或者,你編寫的一個(gè)軟件,某個(gè)付費(fèi)功能只對(duì)已付費(fèi)的用戶 A 開放,但不對(duì)白嫖的用戶 B 開放,其中一種解決方法是:
- 每個(gè)用戶對(duì)應(yīng)一個(gè)維護(hù)分支,用戶 A 對(duì)應(yīng) project_1 分支包含付費(fèi)功能的代碼,用戶 B 對(duì)應(yīng)的 project_2 分支不包含付費(fèi)功能的代碼。
- 當(dāng)用戶 B 付費(fèi)訂閱時(shí),再將付費(fèi)項(xiàng)目的代碼拷貝到 project_2 中
如果再來一個(gè)用戶 C 呢?有沒有感覺很麻煩的樣子?那么我們完全可以這樣做:
#include <stdio.h> int main() { #ifdef CHARGE //付費(fèi)用戶執(zhí)行流程 printf("該用戶已付費(fèi),執(zhí)行付費(fèi)功\n"); #else //白嫖用戶執(zhí)行流程 printf("白嫖用戶,拒絕執(zhí)行付費(fèi)功能\n"); #endif printf("公共功能\n"); return 0; }
在編譯付費(fèi)用戶的時(shí)候,添加 -D CHARGE 參數(shù);編譯白嫖用戶,則不添加。這樣的話,不管來多少用戶,都只需要維護(hù)一個(gè)分支即可。
1.4.2 指定 C 方言(-std)
對(duì)于如下 C 語(yǔ)言代碼:
#include <stdio.h> int main() { for (int i = 1; i <= 3; i++) { printf("i = %d\n", i); } return 0; }
在編譯時(shí)是會(huì)報(bào)錯(cuò)的:
但如果我們加上 -std=c99,就可以了:
二、靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù)
2.1 掃盲
庫(kù)是「已經(jīng)寫好的、供使用的」可復(fù)用代碼,每個(gè)程序都要依賴很多基礎(chǔ)的底層庫(kù)。
從本質(zhì)上,庫(kù)是一種可執(zhí)行代碼的二進(jìn)制形式,可以被操作系統(tǒng)載入內(nèi)存執(zhí)行。程序中調(diào)用的庫(kù)有兩種「靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù)」,所謂的「靜態(tài)、動(dòng)態(tài)」指的是鏈接的過程。
2.2 靜態(tài)庫(kù)
2.2.1 靜態(tài)庫(kù)簡(jiǎn)介
在 Linux 中靜態(tài)庫(kù)以 lib 作為前綴、以 .a 作為后綴,形如 libxxx.a(其中的 xxx 是庫(kù)的名字,自己指定即可)。靜態(tài)庫(kù)以之所以稱之為「靜態(tài)庫(kù)」,是因?yàn)樵阪溄与A段,會(huì)將匯編生成的目標(biāo)文件 .o 與引用的庫(kù)一起鏈接到可執(zhí)行文件中,對(duì)應(yīng)的鏈接方式稱為靜態(tài)鏈接。
2.2.2 靜態(tài)庫(kù)的生成
在 Linux 中靜態(tài)庫(kù)由程序 ar 生成。生成靜態(tài)庫(kù),需要先對(duì)源文件進(jìn)行匯編操作得到二進(jìn)制格式的目標(biāo)文件(以 .o 結(jié)尾的文件),然后再通過 ar 工具將目標(biāo)文件打包就可以得到靜態(tài)庫(kù)文件了。
使用 ar 工具創(chuàng)建靜態(tài)庫(kù)的一般格式為$ ar -rcs libxxx.a 若干原材料(.o文件)
:
2.2.3 靜態(tài)庫(kù)的制作舉例
在某目錄中有如下源文件,用來實(shí)現(xiàn)一個(gè)簡(jiǎn)單的計(jì)算器。
add.c
#include <stdio.h> int add(int a, int b) { return a + b; }
sub.c
#include <stdio.h> int subtract(int a, int b) { return a - b; }
mult.c
#include <stdio.h> int multiply(int a, int b) { return a * b; }
具體操作步驟如下:
# 第一步:將源文件 add.c、sub.c、mult.c 進(jìn)行匯編,得到二進(jìn)制目標(biāo)文件 add.o、sub.o、mult.o $ gcc -c add.c sub.c mult.c # 第二步:將生成的目標(biāo)文件通過 ar 工具打包生成靜態(tài)庫(kù) $ ar rcs libcalc.a add.o sub.o mult.o
2.2.4 靜態(tài)庫(kù)的使用
定義 main 函數(shù)如下所示:
main.c
#include <stdio.h> int main() { int a = 20; int b = 12; printf("a = %d, b = %d\n", a, b); printf("a + b = %d\n", add(a, b)); printf("a - b = %d\n", subtract(a, b)); printf("a * b = %d\n", multiply(a, b)); return 0; }
并將靜態(tài)庫(kù) libcalc.a 置于同級(jí)目錄下:
通過指令$ gcc main.c -o main -L ./ -l calc
編譯 main.c 文件,并鏈接靜態(tài)庫(kù) libcalc.a:
- -L:指定使用的庫(kù)的路徑(因?yàn)樵谕患?jí)目錄下,所以可以直接用了
./
,或者使用絕對(duì)路徑也是可以的) - -l:指定使用的庫(kù)(庫(kù)的名字一定要掐頭去尾。如:libcalc.a 變?yōu)?calc)
編譯結(jié)果會(huì)提示三個(gè) warning,這是由于沒有定義這些函數(shù)導(dǎo)致的,先暫時(shí)不用管。
運(yùn)行 main 結(jié)果如下:
我們思考這么一個(gè)問題:由于靜態(tài)庫(kù)是我們自己制作的,其所包含的函數(shù)我們很清楚,直接鏈接并使用即可。但如果別人想要使用呢?他們可不清楚靜態(tài)庫(kù)中的函數(shù)該如何調(diào)用,所以我們有必要提供一個(gè)頭文件,這樣將靜態(tài)庫(kù)及頭文件交給其他人時(shí),他們知道該如何用了。
head.h
#ifndef _HEAD_H_ #define _HEAD_H_ int add(int a, int b); int subtract(int a, int b); int multiply(int a, int b); #endif
還記得之前的報(bào)錯(cuò)嗎?現(xiàn)在有了頭文件就要使用起來。
main.c
#include <stdio.h> #include "head.h" int main() { int a = 20; int b = 12; printf("a = %d, b = %d\n", a, b); printf("a + b = %d\n", add(a, b)); printf("a - b = %d\n", subtract(a, b)); printf("a * b = %d\n", multiply(a, b)); return 0; }
編譯、鏈接、運(yùn)行,一氣呵成:
2.2.5 ar 命令參數(shù)介紹
制作靜態(tài)庫(kù)時(shí)所使用的指令$ ar rcs libcalc.a add.o sub.o mult.o div.o
共有三個(gè)參數(shù):
-c:創(chuàng)建一個(gè)庫(kù),不管庫(kù)是否存在,都將創(chuàng)建。這個(gè)很好理解,就不做過多的解釋了。
-r:在庫(kù)中插入(替換)模塊 。默認(rèn)新的成員添加在庫(kù)的結(jié)尾處,如果模塊名已經(jīng)在庫(kù)中存在,則替換同名的模塊。
-s:創(chuàng)建目標(biāo)文件索引,這在創(chuàng)建較大的庫(kù)時(shí)能加快時(shí)間。
參數(shù) -r 的詳細(xì)解釋
假設(shè)現(xiàn)在有了新的需求,需要靜態(tài)庫(kù) libcalc.a 提供除法運(yùn)算的功能模塊,該怎么操作呢?
首先我們需要新建一個(gè)除法運(yùn)算的源文件 div.c:
#include <stdio.h> double divide(int a, int b) { return (double)a / b; }
并通過匯編操作生成目標(biāo)文件 div.o。
接下來我們可以通過 -r 參數(shù)將除法運(yùn)算的模塊添加到靜態(tài)庫(kù)中:$ ar -r libcalc.a div.o
。
并且要在 head.h 中增加對(duì)除法運(yùn)算的聲明:
#ifndef _HEAD_H_ #define _HEAD_H_ // Other double divide(int a, int b); #endif
參數(shù) -s 的詳細(xì)解釋
在獲取一個(gè)靜態(tài)庫(kù)的時(shí)候,我們可以通過$ nm -s libcalc.a
來顯示庫(kù)文件中的索引表:
而索引的生成就要?dú)w功于 -s 參數(shù)了。
如果不需要?jiǎng)?chuàng)建索引,可改成 -S 參數(shù)。
如果 libcalc.a 缺少索引,可以使用
$ ranlib libcalc.a
指令添加。
2.2.6 其他命令介紹
# 顯示庫(kù)文件中有哪些目標(biāo)文件,只顯示名稱 $ ar t libcalc.a # 顯示庫(kù)文件中有哪些目標(biāo)文件,顯示文件名、時(shí)間、大小等詳細(xì)信息 $ ar tv libcalc.a # 顯示庫(kù)文件中的索引表 $ nm -s libcalc.a # 為庫(kù)文件創(chuàng)建索引表 $ ranlib libcalc.a
2.3 動(dòng)態(tài)庫(kù)
2.3.1 動(dòng)態(tài)庫(kù)簡(jiǎn)介
在 Linux 中動(dòng)態(tài)庫(kù)以 lib 作為前綴、以 .so 作為后綴,形如 libxxx.so(其中的 xxx 是庫(kù)的名字,自己指定即可)。相比于靜態(tài)庫(kù),使用動(dòng)態(tài)庫(kù)的程序,在程序編譯時(shí)并不會(huì)鏈接到目標(biāo)代碼中,而是在運(yùn)行時(shí)才被載入。不同的應(yīng)用程序如果調(diào)用相同的庫(kù),那么在內(nèi)存中只需要有一份該共享庫(kù)的實(shí)例,避免了空間浪費(fèi)問題。同時(shí)也解決了靜態(tài)庫(kù)對(duì)程序的更新的依賴,用戶只需更新動(dòng)態(tài)庫(kù)即可。
2.3.2 動(dòng)態(tài)庫(kù)的生成
生成動(dòng)態(tài)庫(kù)是直接使用 gcc 命令,并且需要添加 -fpic 以及 -shared 參數(shù):
- -fpic 參數(shù)的作用是使得 gcc 生成的代碼是與位置無關(guān)的,也就是使用相對(duì)位置。
- -shared 參數(shù)的作用是告訴編譯器生成一個(gè)動(dòng)態(tài)鏈接庫(kù)。
2.3.3 動(dòng)態(tài)庫(kù)的制作舉例
還是以上述程序 add.c、sub.c、mult.c 為例:
# 第一步:將源文件 add.c、sub.c、mult.c 進(jìn)行匯編,得到二進(jìn)制目標(biāo)文件 add.o、sub.o、mult.o $ gcc -c -fpic add.c sub.c mult.c # 第二步:將得到的 .o 文件打包成動(dòng)態(tài)庫(kù) $ gcc -shared add.o sub.o mult.o -o libcalc.so # 第三步:發(fā)布動(dòng)態(tài)庫(kù)和頭文件 1. 提供頭文件 head.h 2. 提供動(dòng)態(tài)庫(kù) libcalc.so
至于為什么需要提供頭文件,在講解靜態(tài)庫(kù)時(shí)已經(jīng)做了說明,此處不再贅述。
2.3.4 動(dòng)態(tài)庫(kù)的使用
head.h
#ifndef _HEAD_H_ #define _HEAD_H_ int add(int a, int b); int subtract(int a, int b); int multiply(int a, int b); #endif
main.c
#include <stdio.h> #include "head.h" int main() { int a = 20; int b = 12; printf("a = %d, b = %d\n", a, b); printf("a + b = %d\n", add(a, b)); printf("a - b = %d\n", subtract(a, b)); printf("a * b = %d\n", multiply(a, b)); return 0; }
和靜態(tài)庫(kù)的鏈接方式一樣,都是通過指令$ gcc main.c -o main -L ./ -l calc
來進(jìn)行鏈接庫(kù)操作。
gcc 通過指定的動(dòng)態(tài)庫(kù)信息生成了可執(zhí)行程序 main,但是可執(zhí)行程序運(yùn)行卻提示無法加載到動(dòng)態(tài)庫(kù):
./main: error while loading shared libraries: libcalc.so: cannot open shared object file: No such file or directory
這是怎么回事呢?
2.3.5 解決動(dòng)態(tài)庫(kù)加載失敗的問題
首先來看一下不同庫(kù)的工作原理:
- 靜態(tài)庫(kù)如何被加載:
- 在程序編譯的最后一個(gè)階段也就是鏈接階段,提供的靜態(tài)庫(kù)會(huì)被打包到可執(zhí)行程序中。
- 當(dāng)可執(zhí)行程序被執(zhí)行,靜態(tài)庫(kù)中的代碼也會(huì)一并被加載到內(nèi)存中,因此不會(huì)出現(xiàn)靜態(tài)庫(kù)找不到無法被加載的問題。
- 動(dòng)態(tài)庫(kù)如何被加載:
- 在程序編譯的最后一個(gè)階段也就是鏈接階段,在 gcc 命令中雖然指定了庫(kù)路徑,但是這個(gè)路徑并沒有被記錄到可執(zhí)行程序中,只是檢查了這個(gè)路徑下的庫(kù)文件是否存在。同樣對(duì)應(yīng)的動(dòng)態(tài)庫(kù)文件也沒有被打包到可執(zhí)行程序中,只是在可執(zhí)行程序中記錄了庫(kù)的名字。
- 當(dāng)可執(zhí)行程序被執(zhí)行起來之后:
- 程序會(huì)先檢測(cè)所需的動(dòng)態(tài)庫(kù)是否可以被加載,加載不到就會(huì)提示上邊的錯(cuò)誤信息。
- 當(dāng)動(dòng)態(tài)庫(kù)中的函數(shù)在程序中被調(diào)用了,這個(gè)時(shí)候動(dòng)態(tài)庫(kù)才加載到內(nèi)存,如果不被調(diào)用就不加載。
動(dòng)態(tài)庫(kù)的檢測(cè)和內(nèi)存加載操作都是由動(dòng)態(tài)鏈接器來完成的
動(dòng)態(tài)鏈接器是一個(gè)獨(dú)立于應(yīng)用程序的進(jìn)程,屬于操作系統(tǒng)。當(dāng)用戶的程序需要加載動(dòng)態(tài)庫(kù)的時(shí)候動(dòng)態(tài)連接器就開始工作了,很顯然動(dòng)態(tài)連接器根本就不知道用戶通過 gcc 編譯程序的時(shí)候通過參數(shù) -L 指定的路徑。
那么動(dòng)態(tài)鏈接器是如何搜索某一個(gè)動(dòng)態(tài)庫(kù)的呢,在它內(nèi)部有一個(gè)默認(rèn)的搜索順序,按照優(yōu)先級(jí)從高到低的順序分別是:
可執(zhí)行文件內(nèi)部的 DT_RPATH 段。
系統(tǒng)的環(huán)境變量 LD_LIBRARY_PATH。
系統(tǒng)動(dòng)態(tài)庫(kù)的緩存文件 /etc/ld.so.cache。
存儲(chǔ)「靜態(tài)庫(kù) / 動(dòng)態(tài)庫(kù)」的系統(tǒng)目錄 /lib、/usr/lib 等。
按照以上四個(gè)順序,依次搜索,找到之后結(jié)束遍歷。若檢索到最終還是沒找到,那么動(dòng)態(tài)連接器就會(huì)提示動(dòng)態(tài)庫(kù)找不到的錯(cuò)誤信息。一般情況下,我們都是通過修改系統(tǒng)的環(huán)境變量的方式設(shè)置動(dòng)態(tài)庫(kù)的地址。
將動(dòng)態(tài)庫(kù)路徑追加到環(huán)境變量 LD_LIBRARY_PATH 中:$ LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:動(dòng)態(tài)庫(kù)的絕對(duì)路徑
比如,我所需要的動(dòng)態(tài)庫(kù)的絕對(duì)路徑為 /mnt/hgfs/SharedFolders/DynamicLibrary,那么:
$ LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/mnt/hgfs/SharedFolders/DynamicLibrary
這樣的話,我在運(yùn)行 main,就不會(huì)報(bào)錯(cuò)了。
但是通過這種方式設(shè)置的環(huán)境變量盡在當(dāng)前的終端中有效,那么怎樣才能讓這個(gè)設(shè)置永久生效呢?
通過指令$ vim ~/.bashrc
打開并修改該文件:
修改后,使用$ source ~/.bashrc
使修改立即生效。
經(jīng)過上述操作,就不用每次開啟終端都需要修改環(huán)境變量了。當(dāng)然這種永久生效的方式僅適用于動(dòng)態(tài)庫(kù)路徑唯一的情況,如果你每次使用的動(dòng)態(tài)庫(kù)都在不同的位置,那么這么設(shè)置也沒啥用??
2.4 動(dòng)態(tài)庫(kù)與靜態(tài)庫(kù)的比較
2.4.1 靜態(tài)庫(kù)的特點(diǎn)
- 靜態(tài)庫(kù)對(duì)函數(shù)庫(kù)的鏈接是在編譯期完成的。
- 靜態(tài)庫(kù)在程序編譯時(shí)會(huì)鏈接到目標(biāo)代碼中,因此使可執(zhí)行文件變大。
- 當(dāng)鏈接好靜態(tài)庫(kù)后,在程序運(yùn)行時(shí)就不需要靜態(tài)庫(kù)了。
- 對(duì)程序的更新、部署與發(fā)布不方便,需要全量更新。
- 如果某一個(gè)靜態(tài)庫(kù)更新了,所有使用它的應(yīng)用程序都需要重新編譯、發(fā)布給用戶。
2.4.2 動(dòng)態(tài)庫(kù)的特點(diǎn)
- 動(dòng)態(tài)庫(kù)把對(duì)一些庫(kù)函數(shù)的鏈接載入推遲到程序運(yùn)行時(shí)期。
- 可以實(shí)現(xiàn)進(jìn)程之間的資源共享,因此動(dòng)態(tài)庫(kù)也稱為共享庫(kù)。
- 將一些程序升級(jí)變得簡(jiǎn)單,不需要重新編譯,屬于增量更新。
2.5 使用庫(kù)的目的
在項(xiàng)目中使用庫(kù)一般有兩個(gè)目的:
- 為了使程序更加簡(jiǎn)潔不需要在項(xiàng)目中維護(hù)太多的源文件。
- 另一方面是為了源代碼保密,畢竟不是所有人都想把自己編寫的程序開源出來。
當(dāng)我們拿到了庫(kù)文件(動(dòng)態(tài)庫(kù)、靜態(tài)庫(kù))之后要想使用還必須有這些庫(kù)中提供的 API 函數(shù)的聲明,也就是頭文件,把這些都添加到項(xiàng)目中,就可以快樂的寫代碼了。
參考資料
- GCC | 愛編程的大丙 (subingwen.cn)
- GCC編譯的四個(gè)階段
- Linux 靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù) | 愛編程的大丙 (subingwen.cn)
- linux命令之a(chǎn)r—創(chuàng)建靜態(tài)庫(kù).a文件
- 靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù) - 簡(jiǎn)書 (jianshu.com)
- linux中 ldd命令簡(jiǎn)介
- collect2: error: ld returned 1 exit status(解決方案大總結(jié))
到此這篇關(guān)于GCC 指令詳解及動(dòng)態(tài)庫(kù)、靜態(tài)庫(kù)的使用方法的文章就介紹到這了,更多相關(guān)GCC 指令使用動(dòng)態(tài)庫(kù)、靜態(tài)庫(kù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
kali2021.4a使用virtualenv安裝angr的詳細(xì)過程
在Linux中安裝各種依賴python的軟件時(shí),最頭疼的問題之一就是各個(gè)軟件的python版本不匹配的問題,angr依賴python3,因此考慮使用virtualenv來安裝angr,需要的朋友可以參考下2022-11-11win10環(huán)境安裝kettle與linux環(huán)境安裝kettle的詳細(xì)過程
kettle是一款免費(fèi)開源的、可視化的、國(guó)際上比較流行的、功能強(qiáng)大的ETL必備工具,在ETL這一方面做的還不錯(cuò),下面介紹一下基于win10操作系統(tǒng)安裝kettle和linux操作系統(tǒng)安裝kettle的詳細(xì)過程,感興趣的朋友跟隨小編一起看看吧2022-11-11人工智能開發(fā)語(yǔ)言排行榜: 不死Java, 不朽C/C++, 新貴Python【推薦】
這篇文章主要介紹了人工智能開發(fā)語(yǔ)言排行榜: 不死Java, 不朽C/C++, 新貴Python,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-08-08測(cè)試框架JUnit VS TestNG對(duì)比分析
這篇文章主要為大家介紹了測(cè)試框架JUnit VS TestNG對(duì)比分析詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07使用 FFmpeg 命令拼接mp3音頻文件異常問題及解決方法
這篇文章主要介紹了使用 FFmpeg 命令拼接mp3音頻文件異常問題及解決方法,本文給大家介紹的非常詳細(xì),對(duì)大家的工作或?qū)W習(xí)具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-03-03Typora?0.11.18免費(fèi)版本安裝使用教程(親測(cè)可用)
Typora是一款非常使用的筆記工具,對(duì)于程序員非常友好,在2021年11月23日,Typora?正式發(fā)布?1.0?版本,進(jìn)入了付費(fèi)時(shí)代,Typora免費(fèi)版本0.11.18(最后的免費(fèi)版),本文給大家分享Typora免費(fèi)獲取方法及安裝使用教程,感興趣的朋友參考下吧2022-07-07