文章目錄
一、gcc 演示翻譯環(huán)境
二、動靜態(tài)鏈接庫
三、gcc 選項匯總
一、gcc 演示翻譯環(huán)境
我們寫的代碼如何編譯?這就要用到我們今天講的內(nèi)容 —— gcc 編譯器。
在 Linux 中,C 語言用 gcc 編譯;C++ 用 g++ 編譯。
對于一個 C 程序,從源文件到形成可執(zhí)行程序一共要進行四步:預(yù)處理、編譯、匯編、鏈接 。這四步過程被稱為 翻譯環(huán)境 。
接下來,我們用 gcc 分別演示這四個過程。
1、預(yù)處理
在預(yù)處理中,需要完成頭文件的展開、宏替換、去注釋、條件編譯等工作。
經(jīng)過預(yù)處理后,當前文件還是 C 語言,只不過變得更加精簡。
預(yù)處理指令 :
gcc -E file.c -o file.i
樣例程序 :
進行預(yù)處理 :
預(yù)處理之后生成 .i文件,分別打開 .c 和 .i 文件進行對比 :
我們發(fā)現(xiàn)代碼變?yōu)榘税俣嘈校@是因為頭文件 # 被展開,同時預(yù)處理的其他過程也都進行了。
2、編譯
當程序在編譯時,gcc 會檢查代碼是否有語法錯誤,了解代碼基本內(nèi)容,檢查無誤就會把代碼翻譯為匯編語言。
匯編語言中包含源文件的部分內(nèi)容,所以也可以說該文件為 C 語言匯編代碼 。
編譯指令 :
gcc -S myfile.i -o myfile.s
通過源文件 .c 也可以形成編譯后的文件,指令為 gcc -s .c -o .s,但是我們?yōu)榱搜菔具^程的連續(xù)性,我們統(tǒng)一從上次生成的文件開始執(zhí)行指令。
進行編譯 :
觀察 .s 文件內(nèi)容:
語句含有 c 語言的樣子;而下部分框起來的語句則是匯編代碼,由此證明在編譯過后生成的文件為 C 語言匯編代碼。
3、匯編
匯編之后會把匯編語言轉(zhuǎn)換為二進制文件。
這里的二進制文件可以說是 可重定位二進制文件 。該文件不能被執(zhí)行,類似于 上的 .obj 文件。
這一步知識把我們自己的代碼翻譯為二進制目標文件。
由于機器只認識二進制文件,所以換言之,這一步就是把人能看懂的文件,翻譯成機器能看懂的,就是 生成機器可識別代碼 。
匯編指令 :
gcc -c myfile.s -o test.o
進行匯編 :
觀察.o 內(nèi)容:
打開文件發(fā)現(xiàn)什么都看不懂。
這就是二進制文件,計算機可以識別該文件。
那么該文件可以執(zhí)行嗎?
不可以執(zhí)行,報錯信息為權(quán)限被拒絕,通常為權(quán)限不夠。那這怎么證明該文件不能執(zhí)行?
我們將其提權(quán)試試:
然后再次 ./.o 執(zhí)行該文件:
仍然執(zhí)行失敗,并報錯:無法執(zhí)行該二進制文件。所以這就證明我們的結(jié)論:匯編生成的二進制文件不可執(zhí)行!
4、鏈接
鏈接過程就是將程序和對應(yīng)的庫鏈接起來,編譯器會自動識別語言。
鏈接指令 :
gcc myfile.o -o myfile
進行鏈接 :
再執(zhí)行生成的可執(zhí)行程序 :
5、總結(jié)
我們這邊將翻譯過程按步執(zhí)行只是為了讓小伙伴們理解程序由源代碼到可執(zhí)行程序的一個過程。
實際上,在我們?nèi)粘>幾g代碼只需要 gcc .c 就可以一步生成可執(zhí)行文件。
再提供一個記憶方式:
對于 預(yù)處理、編譯、匯編 三步的選項分別為 ESc ,可以與鍵盤上的 esc 鍵來幫助記憶,區(qū)別前兩個字母是大寫。而生成的文件后綴分別為 iso 可以利用國際標準化組織(ISO)來記憶。
二、動靜態(tài)鏈接庫 1、庫的認識
首先清楚一點,我們寫的代碼里面經(jīng)常會用庫函數(shù),這些調(diào)用接口的方式是我們寫的,但是這些庫函數(shù)的底層實現(xiàn)不是我們寫的,而這中間就有一個調(diào)用庫的過程。
在鏈接的過程中,需要對形成的二進制文件和庫文件進行合并從而形成可執(zhí)行程序,這邊就調(diào)用了庫。庫是能成功編譯的必要條件。
我們一直在使用庫,至少是語言上的庫。
比如上方鏈接生成的可執(zhí)行程序 file ,它就依賴了庫。
通過 ldd ,就可以查看該程序依賴了哪些庫:
不僅程序依賴庫,我們在 linux 下能進行代碼編寫也是因為內(nèi)置了庫。linux 默認有語言級別的頭文件,和語言對應(yīng)的庫。
通過 ls /usr// 就可以看到系統(tǒng)的內(nèi)置的頭文件,我們通過頭文件就可以根據(jù)庫函數(shù),找到對應(yīng)的方法,從而鏈接到正確的庫:
在linux 下,庫分兩種 :
靜態(tài)庫,格式為 .a 。
動態(tài)庫,格式為 .so 。
基于上面的格式,不同的庫還可能會有版本,例如 程序就依賴了libc.so.6 ,后面的 .6 就是版本:
并且我們發(fā)現(xiàn)可執(zhí)行程序依賴的就是 .so 動態(tài)庫。
對于動靜態(tài)庫,以lib 為前綴,.a/.so 為后綴,掐頭去尾就是名稱。例如 libc.so.6 ,它的名字就是 c ,是 c 語言內(nèi)置庫。
而以上動靜態(tài)庫的后綴為 linux 上特有的后綴劃分;如果是 下靜態(tài)庫為 lib ,動態(tài)庫為 dll 。
不止可執(zhí)行程序,就連指令也依賴于庫,我們觀察幾個指令:
常用的 ls, tar 它們都依賴于庫,甚至還依賴于 c 庫。
由此發(fā)現(xiàn),指令和 c 程序一樣,都依賴與庫。所以我們可以將指令看待為:指令是程序,是工具,也是“指令(字面意思)” 。
雖然從編譯角度來說,指令和我們寫的 c 程序沒有任何區(qū)別,但是從功能上來說確是天差地別。
講了這么多c標準庫有靜態(tài)和動態(tài)庫,實際上也就是一句話,庫是必要的,并且?guī)旆譃閯討B(tài)庫和靜態(tài)庫 。
2、鏈接方式
Linux 的鏈接方式就兩種:
而 Linux 默認的鏈接方式為 動態(tài)鏈接 ,我們來證明一下:
用 指令查看 文件的類型:
我們發(fā)現(xiàn)生成的可執(zhí)行程序默認就是采用的 動態(tài)鏈接 。
那么為什么采用靜態(tài)鏈接而不采用動態(tài)鏈接?這里面有什么原因?
我們先了解一下動靜態(tài)庫。
3、動態(tài)庫與靜態(tài)庫
動態(tài)庫對應(yīng)的鏈接方式為動態(tài)鏈接;靜態(tài)庫對應(yīng)的鏈接方式為靜態(tài)鏈接。
動態(tài)鏈接必須使用動態(tài)庫,靜態(tài)鏈接必須使用靜態(tài)庫。
動態(tài)庫優(yōu)缺點:
動態(tài)庫也可以說是共享庫。動態(tài)庫在鏈接的時候,并沒有把相應(yīng)的庫文件加載到可執(zhí)行程序中,而是在運行的時候,拷貝庫中所需代碼的地址到可執(zhí)行程序中相關(guān)的位置。
通過這種鏈接方式,使用動態(tài)庫就大大節(jié)省了內(nèi)存損耗。
但是這也帶來一個缺點,由于動態(tài)庫鏈接時,是通過地址鏈接的。所以只要我們的動態(tài)庫缺失,程序便無法運行。
靜態(tài)庫優(yōu)缺點:
靜態(tài)庫則是在編譯鏈接時c標準庫有靜態(tài)和動態(tài)庫,把庫文件的代碼全部加載到可執(zhí)行程序中。
這種鏈接方式導(dǎo)致生成文件體積龐大。若文件數(shù)目一多,到時候內(nèi)存就會被占用很多。
但是這也有一個優(yōu)點,這樣就使得程序不依賴于庫了。即使庫出了問題,也沒事,因為我們已經(jīng)將代碼加載到程序中了。
通過上面,我們發(fā)現(xiàn),動態(tài)庫可以大大節(jié)省內(nèi)存開銷,并且一般對應(yīng)的動態(tài)庫并不會出問題,再衡量可執(zhí)行程序很多的情況,動態(tài)鏈接其實是最好的鏈接方式。
所以 Linux 默認是采用動態(tài)鏈接的鏈接方式。
4、兩種鏈接方式的使用
對于動態(tài)鏈接,我們已經(jīng)使用過了,比如上文生成的 文件,就是動態(tài)鏈接生成的。
而靜態(tài)鏈接則需要通過特定方式來使用,并且 Linux 默認只裝了動態(tài)庫,靜態(tài)庫是沒裝的,所以先安裝一下:
sudo yum install -y glibc-static
安裝完畢后,就可以使用了。
靜態(tài)鏈接的使用方式為:
gcc myfile.c -o myfile-static -static
我們發(fā)現(xiàn),靜態(tài)鏈接后生成的可執(zhí)行程序的體積非常大。
再用 file - 查看一下文件類型:
就變成 :靜態(tài)鏈接了。
三、gcc 選項匯總
gcc 常用的選項和操作也就是 -o 選項還有 gcc file 直接生成可執(zhí)行程序。
下面對其他選項做出一些補充:
-E :只激活預(yù)處理,這個不生成文件,你需要把它重定向到一個輸出文件里面
-S :編譯到匯編語言不進行匯編和鏈接
-c :編譯到目標代碼
-o :文件輸出到文件
- :此選項對生成的文件采用靜態(tài)鏈接
-g :生成調(diào)試信息。GNU 調(diào)試器可利用該信息
- :此選項將盡量使用動態(tài)庫,所以生成文件比較小,但是需要系統(tǒng)由動態(tài)庫
-O0 :不做任何優(yōu)化,這是默認的編譯選項。
-O1 : 對程序做部分編譯優(yōu)化
-O2 :是比O1更高級的選項,進行更多的優(yōu)化
-O3 :比O2更進一步的進行優(yōu)化
-w :不生成任何警告信息
-Wall: 生成所有警告信息