隨手學習筆記,還沒寫完,希望能比獵人還早寫完
本筆記主要講述從 Program 到 Process 的過程
這是大家一定都看得懂的程式碼,那我們的預處理器會對這段程式碼如何呢?它會把所有巨集(Macro)、標頭檔(其實就是所有#開頭的指令),都置換掉,置換是什麼意思?
讓我們看實際的過程
gcc 是一套強大開源的Compiler Driver,支援數種語言,在這邊我們可以把它看成一套工具來幫助我們做預處理、編譯、組譯、連結就可以了
*.i是經過預處理後的程式碼的副檔名
礙於篇幅關係我不把全部的內容放上來,有興趣的讀者可自己根據上面的指令進行實做。
這邊我們可以看到#define it5 5
和#inculde<stdio.h>
確實都被取代了。
這邊置換成我們想要的結果後,卻多出了一些奇怪的數字那這些數字是什麼呢?
Ans:
The numbers following the filename are flags:
This indicates the start of a new file.
This indicates returning to a file (after having included another file).
This indicates that the following text comes from a system header file, so certain warnings should be suppressed.
This indicates that the following text should be treated as being wrapped in an implicit extern "C" block.
說了那麼多,我們也大致理解預處理器到底在幹嘛了,那可能心中又會有新的疑問
欸,為什麼不直接 Inculde source code 就好,為什麼要創造一個 HeaderFile,然後 Include 所謂的 HeaderFile,這樣感覺有點畫蛇添足欸
關於這個問題我們先來談談標頭檔的意義
就算你 include 標頭檔,Compiler 在編譯時根本不知道你函數正確的位置 (使用動態連結時)
這邊先打個岔,程式分成動態連結和靜態連結,如果是靜態連結那就會在連結的時候把正確位置填入,而動態連結則是運行時填入。
所以下面的討論都是基於動態連結,畢竟靜態連結沒啥好說的,就是直接塞入正確位置
動態連結
靜態連結
沒錯,以範例來說你 include 標頭檔是為了printf()
這個函數,但其實我們去<stdio.h>
裡面查看其實他只有對printf()
進行宣告而已,內部功能其實並沒有在這個檔案裡面,HeaderFile作為編譯前會先被解析的部份,它(HeadFile)作為宣告的集合,是為了讓 Compiler 能認得函數的定義,知道有其函數,Compiler 才會乖乖編譯
那這樣為什麼他能夠置卻進
printf()
,是誰告訴程式函數在哪裡?又是什麼時候告訴程式的?
其實 Program 填寫正確函數位置要等到連結 (Linking) 時才會真正知道函數正確的位置
這部分留著我們後面談到連結時再來詳細介紹,這邊姑且知道編譯器其實並不知道外部函數、變數的位置,一切都要靠比較陌生的動態連結器完成。
這邊再補充一下,其實編譯器是會對
printf()
進行編譯,一般來說呼叫函式的組語是長這樣call [function address]
,只是 function address 不是填入該函數的正確位置,而是一些與組語相關的數值(就是GOT、PLT的位置),連結器則會根據數值和 Relocation table 依序填入函數或變數的正確位置。
好啊,既然連結器那麼厲害能夠知道函數和變數的位置,那回到一開始的問題,一樣都是宣告,比起 HeaderFile 我的.c檔還有定義函數行為,為什麼不
include<xxx.c>
,這樣不是更直觀更方便嗎?
如果我們直接
include<xxx.c>
,那這樣我們當初就沒有分成兩個檔案的必要,我們需要include的原因其一就是希望能讓檔案分離、模組化,如果直接include<xxx.c>確實可以,但這樣就失去include的意義了
延伸閱讀:標頭檔為一個完整的檔案做為插入.c/.cpp檔案中,一般標頭檔的功能為Declaration(宣告),而.c/.cpp作為Defined(定義),此做法可以加快編譯速度也可以避免重複宣告,只要include就可以使用。
Why does C++ need a separate header file?
How to use
他會被分析成這樣
token | 類型 |
---|---|
array | 標識符 |
[ | 左中括號 |
index | 標識符 |
] | 右中括號 |
= | 賦值 |
( | 左小括號 |
index | 標識符 |
+ | 加號 |
4 | 數字 |
) | 右小括號 |
* | 乘號 |
( | 左小括號 |
2 | 數字 |
+ | 加號 |
6 | 數字 |
) | 右小括號 |
從上一個stage接收分析好的記號,並且進行語法分析,產生語法樹(Syntax Tree),分析過程使用上下文無關語法(Context-free Grammer)的分析手段。產生的語法樹是由表達式(Expression)
為節點的樹。
可以看到整個語句被看成賦值表達式(assign expression),賦值
=
的左邊是一個數組表達式,右邊是一個乘法表達式,數組表達式又是由兩個符號表達式所組成的,符號和數字是最小的表達式,他們通常存在在整顆樹的(Leaf Node),另外有些符號有多重含義譬如說 c 語言中的*
,有乘法以及取值(refence)的操作,那就需要在這個階段去分類好。
然後這個也有一個工具叫做yacc(yet another compiler compiler)
語意分析(Semantic Analyze)
產生中間代碼(Generate Middle Code)
最佳化代碼
好懶,隨便先寫一點
關於 Linker 最主要的功能就是把不同的
.o file
連結在一起,並且設定連結靜態庫 & 動態庫
程序員的自我修養:鏈接、裝載與庫