編譯系統
要將C文件轉化成電腦可執行的文件,需要用編譯器將該文件轉化成電腦可讀懂的格式-二進制。我會使用編譯器GCC做例子來讓大家更明白其執行的過程。
首先我們要有一個C文件。
1 | //hello.c |
在Unix裡面執行gcc -o hello hello.c
,此時編譯器便會讀取hello.c且將它轉成二進制文件。轉換過程如下。
這過程總共有4個階段:
預處理階段
預處理器(cpp)會根據C文件裡面的#的命令來讀取Header,例如在這裡會讀取stdio.h, 然後將其內容加入原本的C文件裡面,生成一個以.i作為擴展名的文件。
再來,如果只想進行預處理可輸入gcc -E hello.c -o hello.i
。預處理階段,主要是處理源代碼裡面含有#
字符的指令。
例如: “#include”,”#define”。
編譯階段
然後編譯器會把hello.i轉成hello.s,也就是所謂的匯編語言程序。gcc -S hello.i -o hello.s
變成彙編語言程序後的源代碼如下:
1 | main: |
匯編階段
然後就匯編器就可以派上用場啦,它能將hello.s轉成機械語言指令,再把指令變成可重定位目標程序(Relocatable object program)的格式,存放在hello.o中。as hello.s -o hello.o
鏈接階段
因為這裡helloc程序用了printf函數。此函數其實是本來就屬於標準C Library裡面的一個函數要調用此函數,我們就必須用到鏈接器(ld)將函數合拼到hello.o裡面,完成這過程後,最終也生成了可以被系統執行的hello文件(可執行目標文件)。
由於,鏈接器需要用到一大堆文件把hello.o鏈接起來,才可以把源文件變成最終的可執行目標文件。所以我會在介紹鏈接文件的文章裡面讓大家了解當中的原因。
運行可執行目標文件hello
在命令行輸入./hello
,系統便會運行hello程序,也會返回結果hello,world在你的Shell裡面。
編譯器到底做了什麼?
在整個編譯流程裡面,你的源代碼會經過以下六個步驟變成最終目標代碼。
- 掃描:源代碼被輸入到掃描器內進行詞法分析,系統會用有限狀態機算法分割每一個代碼字符成為一系列的記號(Token)。
- 語法分析:接下來語法分析器(Grammar Parser)負責把那些記號進行語法分析後,會生成語法樹(Syntax Tree)。這裡只針對表達式語法做出分析。
- 語意分析:語意分析器(Semantic Analyzer)負責判定代碼裡面的的靜態語意在語法上是否合法。靜態語意通常是指聲明和類型的轉換。
- 源代碼優化:語法樹會被轉換成被優化後的中間代碼。
- 代碼生成:而中間代碼此時終於可以轉換成目標機器代碼。
- 目標碼優化:簡化目標碼(也是所謂的Assembly Code)。
例子:未優化的代碼1
2
3
4
5movl index, %ecx
addl $4,%ecx
mull $8,%ecx
movl index,%eax
movl %ecx,array(,eax,4)
優化後:1
2
3movl index, %edx
leal 32(,%edx,8),%eax
movl %eax,array(,%edx,4)
這裡只需要了解代碼被優化了,其代碼代表什麼在這文章並沒有討論意義。
系統的硬件組成
包括了總線,I/O設備,主存還有處理器。
總線
I/O設備
主存
處理器
操作系統管理硬件
- 防止硬件被失控的應用程序濫用
- 向應用程序提供簡單一致的機制來控制複雜而又不相同的低級硬件設備
進程
線程
虛擬內存
虛擬內存是一種當CPU內存不夠的時候而需要用到的一種技術,它允許CPU的每一個進程都擁有一個私有的虛擬地址,這樣能使到每一個進程都可以看到一樣的內存空間。下圖是Linux進程使用虛擬地址的例子。至於裡面各區域的詳細解釋以後會在別的文章為你們一一介紹。