天天看點

程式員的自我修養之編譯和連結

一個程式的源代碼到變成可執行程式,要經過 --預處理—編譯—彙編–連結四個過程。

1. 預處理都做了啥?

指令: gcc -E hello.c -o hello.i

預處理主要處理源代碼中以“#”開頭的預編譯指令,主要包括:

  • 将所有的“#define”删除并展開所有的宏定義。 處理所有條件預編譯指令,比如“#if”"#ifdef""#else"“endif”
  • 處理#include預編譯指令,将被包含的檔案插入到該編譯指令的位置。 删除所有的注釋 “//” “”
  • 添加行号和檔案名辨別,比如#2 “hello.c” 2, 以便編譯時編譯器産生調試用的行号資訊*,及用于編譯時,*産生編譯錯誤或警告時能夠顯示行号
  • 保留所有的#pragma編譯器指令,因為編譯器需要使用它們

2. 編譯都做了啥?

指令:gcc -S hello.i -o hello.s

把預處理完的檔案進行一系列詞法分析,文法分析,語義分析,及優化後産生相應的彙編代碼(目标代碼)檔案。

2.1 詞法分析—将源代碼的字元序列掃描分割成一系列的記号。例如:

code: arry[index] = (index + 4) * (2 + 6)

上面語句會被拆分為:

記号 類型
arry 辨別符
[ 左方括号
index 辨別符
] 右方括号

…略

2.2 文法分析

将掃描器産生的記号進行文法分析,進而産生文法樹(syntax tree),文法樹是以表達式(expression)為節點的樹。例如:arry[index] = (index + 4) * (2 + 6)

這是一個包含指派表達式,加法表達式,乘法表達式,數組表達式,括号表達式組成的複雜表達式。

符号和數字是最小的表達式,是以他們是末端子節點。

文法分析過程中會确定運算符号的優先級。

如果發現表達式不合法,比如括号不配對,表達式中缺少操作符,變編譯器将報錯。

程式員的自我修養之編譯和連結

2.3 語義分析

分析整條語句是否有意義,包括:

靜态語義:編譯期間可以确定的語義。通常包括聲明和類型的比對,類型轉換

動态語義:運作期間可以确定的語義。如,0 作為除數。

語義分析後,文法樹的表達式都被标示了類型

程式員的自我修養之編譯和連結

2.4 代碼優化

将文法樹轉換成中間代碼(常見的有三位址碼—(例如 x = y op z)和P-代碼)

中間代碼使得編譯器被分為前端和後端。前端負責産生機器無關的中間代碼。後端負責将中間代碼轉換成目标機器代碼(跨平台編譯發生在後端)

3. 彙編都做了啥?

指令: gcc -c hello.s -o hello.o

彙編是将彙編代碼轉變成機器可以執行的指令,彙編過程沒有複雜的文法,隻是根據彙編指令和機器指令的對照表一一翻譯過來。輸出的是目标檔案(object file)。

4. 連結都做了啥?

連結過程主要包括 位址和空間配置設定,符号決議和重定位。

連接配接器的主要功能是重定位,每個需要修正的地方叫做一個重定位入口(relocation entry),重定位就是給程式中每個這樣的位址引用的位置“打更新檔”,使它們指向正确的位址。

繼續閱讀