現在這個社會充斥著太多的水貨程序員了敷扫,他們不懂任何計算機原理事哭,但是他們依舊做著公司的業(yè)務苫拍,很好的完成老板交代的任務,但是這些東西永遠對他們來說都說一個黑盒子珊肃,他們不會知道為什么會產生段錯誤荣刑,為什么會有懸掛指針,不知道為什么會產生鏈接錯誤伦乔,什么是缺失符號厉亏,更不知道為什么函數 return 一個局部變量就會段錯誤,返回一個字面量常量就不會烈和,當發(fā)生這些問題來只能谷歌爱只,stackoverflow,甚至只能是百度招刹。所以恬试,這里一步一步給大家科普,不斷普及一些“常識”讓大家更好的理解我們編寫的軟件程序疯暑。
計算機程序編譯過程分為4個步驟:
- 預處理
- 編譯
- 匯編
- 鏈接
預處理
$gcc -E hello.c -o hello.i
或
$cpp hello.c > hello.i
預編譯過程主要:
- 處理預編譯指令训柴,例如 #define,#if妇拯,#ifdefine 等幻馁。
- 將 #include 包含文件插入到該預編譯指令的位置
- 刪除所有的注析 // 、/**/
編譯
$gcc –S hello.i –o hello.s
或
$gcc –S hello.c –o hello.s
編譯代表了一整個過程:
- 詞法分析
- 語法分析
- 語義分析
- 源代碼優(yōu)化
- 代碼生成
- 目標代碼優(yōu)化
詞法分析
掃描字節(jié)序并產生記號越锈。
詞法分析產生的記號一般可以分為如下幾類:關鍵字仗嗦、標識符、字面量(包含數字甘凭、字符串等)和特殊符號(如加號儒将、等號)。
語法分析
語法分析器(Grammar Parser)將對由掃描器產生的記號進行語法分析对蒲,從而產生語法樹(Syntax Tree)钩蚊。由語法分析器生成的語法樹就是以表達式(Expression)為節(jié)點的樹。
語義分析
語法分析僅僅是完成了對表達式的語法層面的分析蹈矮,但是它并不了解這個語句是否真正有意義砰逻。
編譯器所能分析的語義是靜態(tài)語義(Static Semantic),所謂靜態(tài)語義是指在編譯期可以確定的語義泛鸟,與之對應的動態(tài)語義(Dynamic Semantic)就是只有在運行期才能確定的語義蝠咆。
靜態(tài)語義通常包括聲明和類型的匹配,類型的轉換。
動態(tài)語義和靜態(tài)語義?
比如將一個浮點型賦值給一個指針的時候刚操,語義分析程序會發(fā)現這個類型不匹配闸翅,編譯器將會報錯。動態(tài)語義一般指在運行期出現的語義相關的問題菊霜,比如將0作為除數是一個運行期語義錯誤坚冀。
中間語言生成
中間代碼使得編譯器可以被分為前端和后端。編譯器前端負責產生機器無關的中間代碼鉴逞,編譯器后端將中間代碼轉換成目標機器代碼记某。這樣對于一些可以跨平臺的編譯器而言,它們可以針對不同的平臺使用同一個前端和針對不同機器平臺的數個后端构捡。
目標代碼生成與優(yōu)化
代碼級優(yōu)化器產生中間代碼標志著下面的過程都屬于編譯器后端液南。編譯器后端主要包括代碼生成器(Code Generator)和目標代碼優(yōu)化器(Target Code Optimizer)。
代碼生成器將中間代碼轉換成目標機器代碼勾徽,這個過程十分依賴于目標機器滑凉。
對于上面例子中的中間代碼,代碼生成器可能會生成下面的代碼序列
movl index, %ecx ; value of index to ecx
addl $4, %ecx ; ecx = ecx + 4
mull $8, %ecx ; ecx = ecx * 8
movl index, %eax ; value of index to eax
movl %ecx, array(,eax,4) ; array[index] = ecx
匯編
$as hello.s –o hello.o
或
$gcc –c hello.c –o hello.o
匯編器(as)將匯編代碼翻譯成機器語言指令喘帚,把這些指令打包成一種叫做可重定位目標程序(relocatable)
的格式畅姊,并將結果保持在目標文件 hello.o 中。hello.o 是一個二進制文件啥辨。
鏈接
$ld -static /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/gcc/i486-linux-gnu/4.1.3/crtbeginT.o -L/usr/lib/gcc/i486-linux-gnu/4.1.3 -L/usr/lib -L/lib hello.o --start-group -lgcc -lgcc_eh -lc --end-group /usr/lib/gcc/i486-linux-gnu/4.1.3/crtend.o /usr/lib/crtn.o
鏈接階段最重要的工作就是重定位,將所有的目標文件都鏈接起來盯腌,在 #include 文件中的函數聲明原本只會生成一個沒有跳轉地址的指令溉知,重定位的工作在其他目標文件中找到這些目標地址,并把地址填補進去腕够。
鏈接分為靜態(tài)鏈接和動態(tài)鏈接级乍,也就是我們通常說的私有對象和共享對象。這里得展開另外一篇來講了帚湘,內容太多玫荣。
鏈接就像將所有的組件合成一起,組成一個整體大诸。