前幾天看了《程序員的自我修養——連接、裝載與庫》中的第二章「編譯和連接」,主要根據其中的內容簡單總結一下C程序編譯的過程吧。html
我如今通常都是用gcc,因此天然以GCC編譯hellworld爲例,簡單總結以下。
前端
hello.c源代碼以下:linux
1 2 3 4 5 6 |
#include <stdio.h> int main() { printf(「Hello, world.\n」); return 0; } |
一般咱們使用gcc來生成可執行程序,命令爲:gcc hello.c,默認生成可執行文件a.out程序員
其實編譯(包括連接)的命令:gcc hello.c 可分解爲以下4個大的步驟:ubuntu
1. 預處理(Preproceessing)ide
預處理的過程主要處理包括如下過程:工具
一般使用如下命令來進行預處理:優化
gcc -E hello.c -o hello.ispa
參數-E表示只進行預處理 或者也可使用如下指令完成預處理過程
cpp hello.c > hello.i /* cpp – The C Preprocessor */
直接cat hello.i 你就能夠看到預處理後的代碼
2. 編譯(Compilation)
編譯過程就是把預處理完的文件進行一系列的詞法分析,語法分析,語義分析及優化後生成相應的彙編代碼。
$gcc –S hello.i –o hello.s
或者
$ /usr/lib/gcc/i486-linux-gnu/4.4/cc1 hello.c
注:如今版本的GCC把預處理和編譯兩個步驟合成一個步驟,用cc1工具來完成。gcc實際上是後臺程序的一些包裝,根據不一樣參數去調用其餘的實際處理程序,好比:預編譯編譯程序cc一、彙編器as、鏈接器ld
能夠看到編譯後的彙編代碼(hello.s)以下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
.file "hello.c" .section .rodata .LC0: .string "Hello, world." .text .globl main .type main, @function main: pushl %ebp movl %esp, %ebp andl $-16, %esp subl $16, %esp movl $.LC0, (%esp) call puts movl $0, %eax leave ret .size main, .-main .ident "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3" .section .note.GNU-stack,"",@progbits |
3. 彙編(Assembly)
彙編器是將彙編代碼轉變成機器能夠執行的命令,每個彙編語句幾乎都對應一條機器指令。彙編相對於編譯過程比較簡單,根據彙編指令和機器指令的對照表一一翻譯便可。
$ gcc –c hello.c –o hello.o
或者
$ as hello.s –o hello.co
因爲hello.o的內容爲機器碼,不能以普通文本形式的查看(vi 打開看到的是亂碼)。
4. 連接(Linking)
經過調用連接器ld來連接程序運行須要的一大堆目標文件,以及所依賴的其它庫文件,最後生成可執行文件。
ld -static crt1.o crti.o crtbeginT.o hello.o -start-group -lgcc -lgcc_eh -lc-end-group crtend.o crtn.o (省略了文件的路徑名)。
helloworld的大致編譯和連接過程就是這樣了,那麼編譯器和連接器到底作了什麼呢?
編譯過程可分爲6步:掃描(詞法分析)、語法分析、語義分析、源代碼優化、代碼生成、目標代碼優化。
詞法分析:掃描器(Scanner)將源代的字符序列分割成一系列的記號(Token)。lex工具可實現詞法掃描。
語法分析:語法分析器將記號(Token)產生語法樹(Syntax Tree)。yacc工具可實現語法分析(yacc: Yet Another Compiler Compiler)。
語義分析:靜態語義(在編譯器能夠肯定的語義)、動態語義(只能在運行期才能肯定的語義)。
源代碼優化:源代碼優化器(Source Code Optimizer),將整個語法書轉化爲中間代碼(Intermediate Code)(中間代碼是與目標機器和運行環境無關的)。中間代碼使得編譯器被分爲前端和後端。編譯器前端負責產生機器無關的中間代碼;編譯器後端將中間代碼轉化爲目標機器代碼。
目標代碼生成:代碼生成器(Code Generator).
目標代碼優化:目標代碼優化器(Target Code Optimizer)。
連接的主要內容是把各個模塊之間相互引用的部分處理好,使得各個模塊之間可以正確地銜接。
連接的主要過程包括:地址和空間分配(Address and Storage Allocation),符號決議(Symbol Resolution),重定位(Relocation)等。
連接分爲靜態連接和動態連接。
靜態連接是指在編譯階段直接把靜態庫加入到可執行文件中去,這樣可執行文件會比較大。
而動態連接則是指連接階段僅僅只加入一些描述信息,而程序執行時再從系統中把相應動態庫加載到內存中去。
靜態連接的大體過程以下圖所示: