編寫的C/C++代碼,通過ide生成一個可執行文件,我們只需要點一下編譯就可以實現,但實際上,我們點下編譯之後,編譯器經歷了很多步驟,才最終生成了我們需要的軟件。
在早期沒有ide或者在Linux上我們自己配置的編譯環境下,需要自己手動去執行預編譯->編譯->彙編->鏈接的步驟,最終生成我們的可執行文件。這有個好處是我們可以更瞭解c語言的編譯原理。
預編譯階段
預編譯階段是將我們的一些預處理指令(頭文件、宏定義等)以及註釋進行處理。在Linux下的預編譯會生成.i文件
gcc -E testCmp.c -o testCmp.i
此步生成的文件內容截取(因爲內容太多了)
extern int ftrylockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;
extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
# 868 "/usr/include/stdio.h" 3 4
# 2 "testCmp.c" 2
# 3 "testCmp.c"
int main()
{
}
預處理指令指的就是我們在程序中在頭部有#標誌的,這是c語言的一個重要的知識,之所以叫他預處理指令,就是因爲他在預編譯階段就被處理,此階段會處理掉一部分內容,這部分內容不會進入到我們之後的步驟當中。
有時候我們在程序中會看到#if、#else,#elif、#endif,這些也是預處理指令,我們將編譯他的階段叫做條件編譯,意思就是如果不符合#if或者#else或者#elif條件的程序部分,根本就不會編譯進最終的可執行軟件當中,這樣可以大大減小我們的機器代碼量。
當然,程序中少不了我們的頭文件,預編譯階段會將頭文件展開,將其中的內容,插入到源文件中。
還有一些比如宏定義,這個階段會將宏定義內容替換到源碼中。
程序中不免有些註釋的內容,我們在初學c語言的時候就聽說,我們加多少註釋都不會影響程序速度,因爲預編譯階段會將註釋內容刪除,在之後的編譯步驟中不會出現註釋。
總結:
1、頭文件展開,內容插入到源文件
2、宏定義內容替換源碼中相應位置
3、刪除註釋
4、條件編譯
編譯階段
編譯階段是將之前經過預編譯階段的代碼,編譯成彙編語言。實際上這一步在網絡上有兩種說法,一種說法是編譯成機器代碼,另一種是編譯成彙編語言,實際上這兩種說法也都沒有錯,只是一個細分和不細分的問題,我想的是既然要講,就要細化的講,我就將編譯階段分爲兩步來說。在Linux上此階段會生成.s文件
gcc -S testCmp.i -o testCmp.s
這個階段會進行一些語法上的詞法分析、語法分析、語義分析、性能優化,經過這個階段,我們的編譯器會認識到我們在語法上的一些錯誤,從而發出警告或者嚴重的會直接拋出錯誤,使得編譯終止。我們來看這一步生成的文件內容
.file "testCmp.c"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0"
.section .note.GNU-stack,"",@progbits
相信電氣專業的朋友一定學過微機原理,裏面兩種處理器8086/88就是使用匯編來寫,當時上大學這個頭痛,因爲彙編實在是不太符合寫代碼的思想。實際上彙編已經屬於相對底層的語言了,現在我們使用最廣泛的接近底層的代碼就是c語言,本文不會對彙編進行深入剖析,有興趣的朋友可以在網上了解,不過不太建議深入學,除非真的有需要。
總結:此步驟會檢測我們的語法問題,也會進行一些優化處理,最終生成彙編語言文件
彙編階段
彙編階段會將我們上一步的彙編語言編譯成目標文件。這個文件的內容爲機器可以識別的二進制代碼,非超底層程序員根本就不會看的懂這部分的代碼,因爲發明c語言這些可視化語言的目的就是爲了防止與二進制代碼直接打交道(頭痛~)。
此步在windows下生成.obj,在Linux平臺下生成.o
gcc -c testCmp.s -o testCmp.o
此步生成的文件是不能通過查看普通文件的方式被查看的,要使用專門的二進制查看軟件查看,我這臺電腦上沒有查看器,所以也就不在這裏列出了,朋友們只需要知道是一堆二進制代碼就對了。
總結:生成機器可以識別的二進制代碼
鏈接階段
鏈接的目的是將一些依賴的庫與我們的源文件合成最終生成可執行文件。我們將鏈接分爲靜態鏈接和動態鏈接,靜態鏈接是依靠一個獨立的鏈接器,將庫文件與代碼源文件合成生成一個獨立的程序,這個有個弊端就是會導致我們的可執行程序很大,因爲庫文件本身就很大;另一個動態鏈接,動態鏈接庫文件不會被編譯到最終的軟件中,而是在可執行軟件中生成一個描述信息,我們根據信息去調用外部的動態鏈接庫。
.lib和.a分別是windows和Linux下的靜態鏈接庫拓展名,.dll和.so分別是windows和Linux下的動態鏈接庫拓展名。
Linux下執行生成最終目標文件
gcc testCmp.o -o testCmp
總結:此步分別使用動態鏈接庫和靜態鏈接庫支持我們的軟件,並生成最終軟件