編譯器、鏈接器和解釋器

編譯器

編譯器的作用就是將高級編程語言翻譯爲機器代碼。

編譯器工作過程一般分爲:

  • 詞法分析:將高級語言解析成 Token 集合;
  • 語法分析:將 Token 集合構建成語法樹,在這個過程可以判斷出語法是否有誤,比如 while 後面是否 { 等等;
  • 語義分析:判斷語法樹是否有明顯的語義錯處,比如:string 不能與 number 相加;
  • 中間代碼生成:在一些編譯器中,源代碼可能會被轉換成中間代碼,這是一種介於高級語言和底層機器代碼之間的表示形式。中間代碼易於優化和跨平臺生成。
  • 優化:編譯器會進行一系列的優化操作,以提高生成的機器代碼的性能。這包括代碼消除、循環展開、內聯函數等優化技術。
  • 目標代碼生成:目標代碼生成階段將中間代碼或其他中間表示翻譯爲特定體系結構的機器代碼。這些機器代碼可以由計算機直接執行。

鏈接器

編譯器生成了一堆二進制文件,怎麼運行這些二進制文件呢?鏈接器的作用就是將多個目標文件(object files)鏈接爲一個可執行文件或庫。

1. 符號解析(Symbol Resolution):

符號指的是全局變量函數

每個文件都要確認兩個事,自己有哪些符號可以供別的文件使用引用別的文件的符號真實存在

鏈接器會從目標文件和庫文件中提取這些符號,並建立符號表,記錄每個符號的名稱和地址。如果有多個目標文件或庫中存在相同名稱的符號,鏈接器會根據不同的規則解決衝突。

目標文件通常是由編譯器生成的二進制文件,包含函數和變量的定義以及對其他符號的引用;而庫文件則包含預編譯的目標文件(靜態鏈接,如 .a.lib 文件)。

2. 重定位(Relocation):

目標文件和庫文件通常會包含相對於文件起始位置的相對地址,這些地址需要在最終可執行文件中被映射到正確的內存地址上。鏈接器會遍歷目標文件中的重定位信息(.relo.text.relo.data),將這些相對地址替換爲實際的絕對地址。這樣,可執行文件就可以正確地在內存中加載和執行。

3. 庫依賴解析(Library Dependency Resolution):

3.1 靜態鏈接(Static Linking):
在靜態鏈接中,鏈接器會將程序所依賴的庫(如 .a.lib 文件)的代碼和數據直接嵌入到最終的可執行文件中。當您運行可執行文件時,不需要額外加載外部的庫文件,因爲所有需要的代碼和數據已經在可執行文件內部。

3.2 動態鏈接(Dynamic Linking):
在動態鏈接中,可執行文件只包含對庫函數和變量的引用,而不包含實際的庫代碼和數據。這些庫代碼和數據存儲在系統的共享庫中(也稱爲動態鏈接庫或共享對象,如 .so.dll 文件)。多個程序可以共享同一個庫的實例,減少了存儲空間和系統資源的浪費。

動態鏈接可能發生在兩個時機:

  • 加載時的動態鏈接:操作系統會在執行可執行文件之前,將所需的共享庫加載到內存中。這時,鏈接器會解析可執行文件中的引用,將這些引用關聯到所加載的共享庫中的實際函數和變量。
  • 運行時的動態鏈接:共享庫已經在加載時加載到了內存中,但鏈接的最終步驟是在程序運行時進行的。這時,操作系統會確保程序可以正確地訪問所需的共享庫中的函數和變量。程序在運行期間,可以根據需要調用共享庫中的函數,操作系統會負責將這些調用關聯到實際的庫代碼。

4. 生成可執行文件(Executable File Generation):

在完成所有的符號解析、重定位和庫依賴解析後,鏈接器會根據上述步驟的結果生成最終的可執行文件。這個文件包含了所有目標文件和庫文件的代碼和數據,以及鏈接器添加的一些元信息。

可執行文件其實和目標文件是很相似的,都有代碼區和數據區,只不過在可執行文件中還有一個特殊的符號 _start,CPU 正是從這個地址開始執行機器指令的,經過一系列的準備工作後正式從程序的 main 函數開始運行。

解釋器

解釋器是一種能夠直接執行源代碼的程序或系統組件。

解釋器會逐行讀取源代碼,並將其翻譯爲機器指令或直接在虛擬機中執行。因此,您可以在沒有編譯步驟的情況下運行源代碼。

一些解釋性語言具有良好的跨平臺性,因爲解釋器可以在不同的操作系統上運行。這使得編寫一次代碼,多平臺運行成爲可能。

一些典型的解釋性編程語言包括 Python、Ruby、JavaScript、Perl 等。這些語言通常用於腳本編程、Web 開發、數據分析等領域。

JVM(Java虛擬機)可以被看作是一種解釋器。JVM 是用於執行 Java 程序的虛擬機,它將 Java 源代碼編譯成字節碼(Java 中間代碼),然後在運行時通過解釋器將字節碼轉換爲機器指令執行。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章