天天看點

C語言基礎——編譯器工作原理

代碼編譯過程分為四個階段:

  1. 預處理階段
  2. 編譯階段
  3. 彙編階段
  4. 連結階段

接下來我們寫一段簡單的代碼 demo.c 看一下編譯器的工作過程,這裡用的是ubuntu裡面的vim編輯器以及gcc編譯器。

#include <stdio.h>
#define MAX_NUM 100
int main()
{
        //列印MAX_NUM
        printf("MAX_NUM=%d\n",MAX_NUM);

        int a=1,b=2;

        int c=a+b;
        printf("c=%d\n",c);
        
        return 0;
}
           

以上就是demo.c的代碼。

下面,我們将通過控制gcc編譯器的參數,讓gcc編譯器一步一步的編譯,而不是一次性完成編譯。首先,簡單看一下gcc的參數:

C語言基礎——編譯器工作原理

在ubuntu裡面輸入指令

man gcc

即可找到gcc編譯器的參數。我們将使用的是最前面的-C,-S,-E指令。下面我們開始用指令來控制。

1.預處理階段

輸入指令:

gcc -E demo.c -o demo.i

,得到的demo.i檔案就是預處理之後的檔案。我們可以進入這個檔案。

C語言基礎——編譯器工作原理

進入demo.i,我們可以看到,預處理階段将頭檔案、宏定義展開,并且删除了注釋部分,而main()函數部分還是c語言代碼。是以,預處理階段的任務的就是:

  • 展開頭檔案
  • 展開宏定義
  • 删除所有注釋

2.編譯階段

輸入指令:

gcc -S demo.i -o demo.S

,得到的demo.S檔案就是編譯階段的檔案,.S字尾名表示的就是彙編代碼。同樣我們可以進入demo.S:

C語言基礎——編譯器工作原理

進入之後可以發現,代碼都已經變成了彙編代碼。這一階段的主要任務就是:

  • 檢查文法錯誤
  • 翻譯成彙編語言

3.彙編階段

輸入指令:

gcc -C demo.S -o demo.o

,得到的demo.o就是彙編階段後的代碼:

C語言基礎——編譯器工作原理

我們可以看到,這個時候已經是機器碼了。但是這個檔案能不能直接運作呢?我們輸入指令:

./demo.o

試一下:

C語言基礎——編譯器工作原理

這裡會報錯。報錯就說明.o不是最終能跑起來的檔案。但是為什麼會報錯呢?這已經是機器碼了,為什麼還是不能直接運作呢?

因為我們看不懂機器碼,是以我們先用反彙編來看看這個時候demo.o裡面的内容。輸入反彙編指令:

objdump -d demo.o

C語言基礎——編譯器工作原理

系統裡面會有兩套位址,一套是系統自己用的的預設位址,一套是使用者代碼運作時用的記憶體位址。cpu是跳到記憶體裡拿位址的,代碼運作時,作業系統會把他放到使用者代碼運作的記憶體空間裡,是以,編譯器編譯後的最終能運作的那個檔案裡面的位址不能是系統預設位址,而應該是記憶體位址。

demo.o裡面配置設定的是預設位址,作業系統把demo.o拿到記憶體時,不知道放在哪裡(因為demo.o裡面不是記憶體位址),是以無法運作。

為了把代碼放到記憶體裡,要給代碼的每一條指令連結一個記憶體位址,連結後的位址就是其在記憶體中存放的位址。這就是編譯的第4個階段。

彙編階段系統主要做的是

  • 将彙編語言生成機器指令

4.連結階段

輸入指令

gcc demo.o -o demo.elf

,這時候的.elf檔案就是最終能運作的檔案。我們可以反彙編一下這個檔案,輸入反彙編指令:

objdump -d demo.elf

,可以看到:

C語言基礎——編譯器工作原理

連結階段主要做了三件事

  • 添加位址:把各種位址資訊添加到headinfo中
  • 補代碼:補充的是固定的程序的啟動代碼和初始化代碼
  • 分段:一種技術,将機器碼連結成段

總結

  1. 上述是用代碼控制了編譯過程。如果想要一步到位,我們可以直接輸入指令

    gcc demo.c -o demo.elf

    ,後面的demo.elf,就算不加字尾名,預設也是elf的檔案。
  2. 學習c語言不能單單學習文法,還要學習其他相關的基礎知識。編譯器是學習任何一門都需要接觸的,是以了解編譯器的工作原理也是很有必要的。

以上就是文章的主要内容,如有錯誤,請各位朋友給我留言嗷。謝謝你們~

繼續閱讀