首頁  >  文章  >  把高階語言編寫的原始程式轉換為可執行程序,要經過什麼?

把高階語言編寫的原始程式轉換為可執行程序,要經過什麼?

青灯夜游
青灯夜游原創
2020-08-31 15:44:1652681瀏覽

把高階語言寫的原始程式轉換為可執行程序,要經過「編譯與連線」。用高階語言編寫的原始程式不能在機器上直接執行,必須經過編譯和連接。

把高階語言編寫的原始程式轉換為可執行程序,要經過什麼?

程式要執行起來,必須經過四個步驟:預處理、編譯、彙編和連結。接下來透過幾個簡單的例子來詳細解說這些過程。

對於上邊用到的幾個選項需要說明一下。

使用gcc 指令不跟任何的選項的話,會預設執行預處理、編譯、彙編、連結這整個過程,如果程式沒有錯,就會得到一個可執行文件,預設為a.out

-E選項:提示編譯器執行完預處理就停下來,後邊的編譯、組譯、連結就先不執行了。

-S選項:提示編譯器執行完編譯就停下來,不去執行組譯和連結了。

-c選項:提示編譯器執行完組譯就停下來。

所以,這三個選項相當於限定了編譯器執行操作的停止時間,而不是單獨的將某一步拎出來執行。

上述程式的執行過程大家應該都很熟悉了,就不浪費口舌了。

一、預處理:

       使用-E選項,表示只進行預編譯,對應產生一個 .i 檔案。

預處理程序進行的操作:

  • 將所有的「#define」刪除,並且展開所有的巨集定義
  • 處理所有的條件編譯指令,例如「#if」、「#ifdef」、「#elif」、「#else」、「#endif」
  • 處理「#include」預編譯指令,將被包含的頭檔插入到該編譯指令的位置。 (這個過程是遞歸進行的,因為被包含的檔案可能也包含了其他檔案)
  • 刪除所有的註解「//」和「/* */」。
  • 新增行號和檔名標識,方便後邊編譯時編譯器產生偵錯用的行號心意以及編譯時產生編譯錯誤或警告時能夠顯示行號。
  • 保留所有的#pragma編譯指令,因為編譯器需要使用它們。

使用一個簡單的程序來驗證一下事實是否如上述所說的一樣

編寫一個簡單的程序,然後使用-E選項執行預處理過程,打開生成的.i 檔案與原始檔案進行比對,結果一目了然

       對於給程式碼加上行號這個就不在這裡示範了,我們在寫程式碼的時候是不會手動加上行號的,我們看到的行號都是自己使用的編輯工具自動加上的,而這些行號編譯系統是看不到的,但是呢,我們發現如果我們哪一行的程式碼出現了問題,編譯的時候就會給提示說哪行的程式碼有什麼問題,這就已經證明,編譯器是會自動加行號的。

二、編譯:

        使用-S選項,表示編譯作業執行完就結束。對應產生一個 .s 檔。

        編譯過程是整個程式建構的核心部分,編譯成功,會將原始碼由文字形式轉換成機器語言,編譯過程就是把預處理完的檔案進行一系列字法分析、文法分析、語意分析以及優化後產生相應的彙編程式碼檔案。

  • 詞法分析:

        詞法分析是使用一種稱為lex的程式實現詞法掃描,它會依照使用者先前描述好的詞法規則將輸入的字串分割成一個個記號。產生的記號一般分為:關鍵字、識別碼、字面量(包含數字、字串等)和特殊符號(運算子、等號等),然後他們放到對應的表中。

  • 語法分析:語法分析器根據使用者給定的語法規則,將詞法分析產生的記號序列進行解析,然後將它們構成一棵語法樹。對於不同的語言,只是其語法規則不一樣。用於語法分析也有現成的工具,叫做:yacc。

#
  • 語意分析:

       語法分析完成了對表達式語法層面的分析,但它不了解這個語句是否真正有意義。有的語句在語法上是合法的,但是卻是沒有實際的意義,比如說兩個指標的做乘法運算,這個時候就需要進行語意分析,但是編譯器能分析的語意也只有靜態語意。

       靜態語意:編譯期就可以確定的語意。通常包括聲明與類型的匹配、類型的轉換。例如當一個浮點型的表達式賦值給一個整數的表達式時,其中隱含一個從浮點型到整數的轉換,而語意分析就需要完成這個轉換,再比如,將一個浮點型的表達式賦值給一個指針,這肯定是不行的,語意分析的時候就會發現兩者型別不匹配,編譯器就會報錯。

       動態語意:只有在運作期才能決定的語意。比如說兩個整數做除法,語法上沒問題,類型也匹配,聽著好像沒毛病,但是,如果除數是0的話,這就有問題了,而這個問題事先是不知道的,只有在運行的時候才能發現他是有問題的,這就是動態語意。

  • 中間程式碼產生

       我們的程式碼是可以進行最佳化的,對於一些在編譯期間就能確定的值,是會將它進行最佳化的,例如說上邊例子中的2 6,在編譯期間就可以確定他的值為8了,但是直接在語法上進行優化的話比較困難,這時優化器會先將語法樹轉成中間代碼。中間程式碼一般與目標機器和運作環境無關。 (不包含資料的尺寸、變數位址和暫存器的名字等)。中間程式碼在不同的編譯器中有著不同的形式,比較常見的有三位址碼和P-程式碼。

       中間程式碼讓編譯器可分割為前端與後端。編譯器前端負責產生於機器無關的中間程式碼,編譯器後端將中間程式碼換成機器碼。

  • 目標程式碼產生與最佳化

程式碼產生器將中間程式碼轉換為機器碼,這個過程是依賴目標機器的,因為不同的機器有著不同的字長、暫存器、資料型別等。

最後目標程式碼最佳化器對目標程式碼進行最佳化,例如選擇合適的尋址方式、使用唯一來取代乘除法、刪除出多餘的指令等。

三、彙編

彙編過程呼叫彙編器as來完成,是用於將彙編程式碼轉換成機器可以執行的指令,每一個彙編語句幾乎都對應一條機器指令。

使用指令as hello.s -o hello.o 或使用gcc -c hello.s -o hello.o來執行到組譯過程結束,對應產生的檔案是.o檔。

四、連結

連結的主要內容就是將各個模組之間相互引用的部分正確的銜接起來。它的工作就是把一些指令對其他符號位址的引用加以修正。連結過程主要包括了地址和空間分配、符號決議和重定向

符號決議:有時候也被叫做符號綁定、名稱綁定、名稱決議、或地址綁定,其實就是指用符號來去標識一個位址。

                比如說int a = 6;這樣一個代碼,用a來識別一個塊4個位元組大小的空間,空間裡邊存放的內容就是4.

#重定位:重新計算計算

各個目標的位址過程叫做重定位。

最基本的鏈接叫做靜態鏈接,就是將每個模組的源代碼文件編譯成目標文件(Linux:.o  Windows:.obj),然後將目標文件和庫一起鏈接形成最後的可執行檔。庫其實就是一組目標檔案的包,就是一些最常用的程式碼變異成目標檔案後打包存放。最常見的庫是運行時庫,它是支援程式運行的基本函數的集合。

更多相關知識,請造訪:PHP中文網

! ###

以上是把高階語言編寫的原始程式轉換為可執行程序,要經過什麼?的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn