汽車上使用的ECU中運行的程序,是軟件工程師基于C/C++語言編寫出來,然后通過編譯器編譯得到可執(zhí)行文件,最后將可執(zhí)行文件刷寫入ECU中實現(xiàn)的,今天我們介紹下編譯過程。
通常我們使用GCC編譯器來實現(xiàn)編譯,對于GCC編譯器,我們就不展開介紹了,感興趣的同學(xué)可以到網(wǎng)上找些材料,自行充電。
0 四個階段
雖然我們稱GCC是C語言的編譯器,但使用GCC由C語言源代碼文件生成可執(zhí)行文件的過程不僅僅是編譯的過程,而是要經(jīng)歷四個相互關(guān)聯(lián)的步驟∶
GCC首先調(diào)用cpp進(jìn)行預(yù)處理,在預(yù)處理過程中,對源代碼文件中包含的預(yù)編譯語句進(jìn)行分析。然后調(diào)用編譯器進(jìn)行編譯,這個階段根據(jù)輸入文件生成以.s為后綴的匯編文件。匯編過程是針對匯編語言的步驟,將.S為后綴的匯編語言源代碼文件生成以.o為后綴的目標(biāo)文件。當(dāng)所有的目標(biāo)文件都生成之后,GCC會完成最后的鏈接過程,最終生成可執(zhí)行文件。
1 預(yù)處理階段
讀取c源程序,對其中的偽指令(以#開頭的指令)和特殊符號進(jìn)行處理。那么,什么是偽指令呢?偽指令主要包括以下四個方面:
(1)宏定義指令,如#define Demu 520,#undef等。對于前一個偽指令,預(yù)編譯所要做的是將程序中的所有Demu用520替換,但作為字符串常量的Demu則不被替換。對于后者,則將取消對某個宏的定義,使以后該串的出現(xiàn)不再被替換。
(2)條件編譯指令,如#ifdef,#ifndef,#else,#elif,#endif等。這些條件編譯指令的引入使得程序員可以通過定義不同的宏來決定編譯程序?qū)δ男┐a進(jìn)行處理。預(yù)編譯程序?qū)⒏鶕?jù)有關(guān)的文件,將那些不必要的代碼過濾掉。
(3)頭文件包含指令,如#include "Filename"或者#include 等。在頭文件中一般用偽指令#define定義了大量的宏(最常見的是字符常量),同時包含有各種外部符號的聲明。采用頭文件的目的主要是為了使某些定義可以供多個不同的C源程序使用。因為在需要用到這些定義的C源程序中,只需加上一條#include語句即可,而不必再在此文件中將這些定義重復(fù)一遍。預(yù)編譯程序?qū)杨^文件中的定義統(tǒng)統(tǒng)都加入到它所產(chǎn)生的輸出文件中,以供編譯程序?qū)χM(jìn)行處理。
包含到c源程序中的頭文件可以是系統(tǒng)提供的。在程序中#include它們要使用尖括號(<>)。另外開發(fā)人員也可以定義自己的頭文件,這些文件一般與c源程序放在同一目錄下,此時在#include中要用雙引號("")。所以,你知道為什么include的頭文件,尖括號和雙引號都有了吧,當(dāng)然這是規(guī)范用法,大家敲代碼的時候最好也按照這個規(guī)范來。
(4)特殊符號,預(yù)編譯程序可以識別一些特殊的符號。例如在源程序中出現(xiàn)的LINE標(biāo)識將被解釋為當(dāng)前行號(十進(jìn)制數(shù)),F(xiàn)ILE則被解釋為當(dāng)前被編譯的C源程序的名稱。預(yù)編譯程序?qū)τ谠谠闯绦蛑谐霈F(xiàn)的這些串將用合適的值進(jìn)行替換。
預(yù)編譯程序所完成的基本上是對源程序的“替代”工作。經(jīng)過此種替代,生成一個沒有宏定義、沒有條件編譯指令、沒有特殊符號的輸出文件。這個文件的含義同沒有經(jīng)過預(yù)處理的源文件是相同的,但內(nèi)容有所不同。下一步,此輸出文件將作為編譯程序的輸出而被翻譯成為機(jī)器指令。
2 編譯階段
這個階段,編譯器將預(yù)處理后的輸出文件進(jìn)行編譯處理和優(yōu)化處理。
編譯程序所要做的工作就是通過詞法分析和語法分析,在確認(rèn)所有的指令都符合語法規(guī)則之后,將其翻譯成等價的中間代碼表示或匯編代碼。
2.1 詞法分析
詞法分析的任務(wù)是:輸入源程序,對構(gòu)成源程序的字符串進(jìn)行掃描和分解,識別出一個個的單詞(亦稱單詞符號),如關(guān)鍵字(if,else,for,while)、標(biāo)識符、常數(shù)、運算符和界符(標(biāo)點符號、左右括號)。
單詞符號是語言的基本組成成分,是人們理解和編寫程序的基本要素。識別和理解這些要素?zé)o疑也是翻譯的基礎(chǔ)。如同將英文翻譯成中文的情形一樣,如果你對英語單詞不理解,那就談不上進(jìn)行正確的翻譯。在詞法分析階段的工作中所依循的是語言的詞法規(guī)則(也稱構(gòu)詞規(guī)則)。
2.2 語法分析
語法分析的任務(wù)是:在詞法分析的基礎(chǔ)上,根據(jù)語言的語法規(guī)則,把單詞符號串分解成各類語法單位(語法范疇),如“短語”、“句子”、“程序段”和“程序”等。通過語法分析,確定整個輸入串是否構(gòu)成語法上正確的“程序”。語法分析所依循的是語言的語法規(guī)則。語法規(guī)則通常用上下文無關(guān)文法描述。詞法分析是一種線性分析,而語法分析是一種層次結(jié)構(gòu)分析。
例如:
Z = X + 0.618 * Y;
代表一個“賦值語句”,而其中的X + 0.618 * Y 代表一個“算術(shù)表達(dá)式”。因而,語法分析的任務(wù)就是識別X + 0.618 * Y為算術(shù)表達(dá)式,同時,識別整個符號串屬于賦值語句的范疇。
2.3 優(yōu)化處理
優(yōu)化處理是編譯系統(tǒng)中一項比較深奧的技術(shù)。它涉及到的問題不僅同編譯技術(shù)本身有關(guān),而且同機(jī)器的硬件環(huán)境也有很大的關(guān)系。優(yōu)化一方面是對中間代碼的優(yōu)化,不依賴于具體的計算機(jī)。另一種優(yōu)化則主要針對目標(biāo)代碼的生成而進(jìn)行的。
對于前一種優(yōu)化,主要的工作是刪除公共表達(dá)式、循環(huán)優(yōu)化(代碼外提、強(qiáng)度削弱、變換循環(huán)控制條件、已知量的合并等)、復(fù)寫傳播,以及無用賦值的刪除等。
后一種類型的優(yōu)化同機(jī)器的硬件結(jié)構(gòu)密切相關(guān),最主要的是考慮是如何充分利用機(jī)器的各個硬件寄存器存放有關(guān)變量的值,以減少對于內(nèi)存的訪問次數(shù)。另外,如何根據(jù)機(jī)器硬件執(zhí)行指令的特點對指令進(jìn)行一些調(diào)整使目標(biāo)代碼比較短,執(zhí)行的效率比較高,這一點非常重要。
2.4 中間代碼生成
對語法分析所識別出的各類語法范疇,分析其含義,然后進(jìn)行初步翻譯,產(chǎn)生中間代碼。這一階段通常包含兩個方面的工作。
首先,對每種語法范疇進(jìn)行語義i安插,例如,變量是否定義、類型是否正確等等。如果語義正確,則進(jìn)行另一方面工作,即進(jìn)行中間代碼的解釋。這一階段所依循的是語言的語義規(guī)則。通常使用屬性文法描述語義規(guī)則。
“翻譯”僅僅在這里才開始涉及到。所謂“中間代碼”是一種含義明確、便于處理的記號系統(tǒng),它通常獨立于具體的硬件。這種記號系統(tǒng)或者與現(xiàn)代計算機(jī)的指令形式比較接近,或者能夠比較容易地把它變換成現(xiàn)代計算機(jī)的機(jī)器指令。例如,許多編譯程序采用了“四元式”作為中間代碼。這種四元式的形式是:
算符/左操作數(shù)/右操作數(shù)/結(jié)果
它的意義是:對“左右操作數(shù)”進(jìn)行某種運算(由“算符”指明),把運算所得的值作為“結(jié)果”保留下來。在采用四元式作為中間代碼的情形下,中間代碼產(chǎn)生的任務(wù)就是按語言的語法規(guī)則把各類范疇翻譯成四元式序列。
例如,下面的賦值語句:
Z = (X + 0.618) * Y / W;
可被翻譯為如下的四元式序列:
序號 | 算符 | 左操作 | 右操作 | 結(jié)果 |
---|---|---|---|---|
(1) | + | X | 0.618 | T1 |
(2) | * | T1 | Y | T2 |
(3) | / | T2 | W | Z |
其中,T1和T2是編譯期間引進(jìn)的臨時工作變量;第一個四元式意味著把X的值加上0.618存放在T1中;第二個四元式值將T1的值和Y的值相乘存于T2中;第三個四元式指將T2的值除以Y的值留結(jié)果于Z中。
一般而言,中間代碼是一種獨立于具體硬件的記號系統(tǒng)。常用的中間代碼,除了四元式之外,還有三元式、間接三元式、逆波蘭記號和樹形表示等等。
這樣,經(jīng)過以上分析和優(yōu)化后,匯編代碼經(jīng)過匯編程序的匯編轉(zhuǎn)換成相應(yīng)的機(jī)器指令,才可能被機(jī)器執(zhí)行。
3 匯編階段
匯編過程實際上指把匯編語言代碼翻譯成目標(biāo)機(jī)器指令的過程。對于被翻譯系統(tǒng)處理的每一個C語言源程序,都將最終經(jīng)過這一處理而得到相應(yīng)的目標(biāo)文件。目標(biāo)文件中所存放的也就是與源程序等效的目標(biāo)的機(jī)器語言代碼。
目標(biāo)文件由段組成。通常一個目標(biāo)文件中至少有兩個段:
代碼段: 該段中所包含的主要是程序的指令。該段一般是可讀和可執(zhí)行的,但一般卻不可寫。
數(shù)據(jù)段: 主要存放程序中要用到的各種全局變量或靜態(tài)的數(shù)據(jù)。一般數(shù)據(jù)段都是可讀,可寫,可執(zhí)行的。
嵌入式系統(tǒng)中主要有三種類型的目標(biāo)文件:
(1)可重定位文件(relocatable)
其中包含有適合于其它目標(biāo)文件鏈接來創(chuàng)建一個可執(zhí)行的或者共享的目標(biāo)文件的代碼和數(shù)據(jù)。
(2)共享的目標(biāo)文件(shared)
這種文件存放了適合于在兩種上下文里鏈接的代碼和數(shù)據(jù)。第一種是鏈接程序可把它與其它可重定位文件及共享的目標(biāo)文件一起處理來創(chuàng)建另一個目標(biāo)文件;第二種是動態(tài)鏈接程序?qū)⑺c另一個可執(zhí)行文件及其它的共享目標(biāo)文件結(jié)合到一起,創(chuàng)建一個進(jìn)程映象。
(3)可執(zhí)行文件(executable)
它包含了一個可以被操作系統(tǒng)創(chuàng)建一個進(jìn)程來執(zhí)行之的文件。
匯編程序生成的實際上是第一種類型的目標(biāo)文件。對于后兩種還需要其他的一些處理方能得到,這個就是鏈接程序的工作了。
4 鏈接階段
由匯編程序生成的目標(biāo)文件并不能立即就被執(zhí)行,其中可能還有許多沒有解決的問題。
例如,某個源文件中的函數(shù)可能引用了另一個源文件中定義的某個符號(如變量或者函數(shù)調(diào)用),在程序中可能調(diào)用了某個庫文件中的函數(shù)等。所有的這些問題,都需要經(jīng)鏈接程序的處理方能得以解決。
鏈接程序的主要任務(wù)是將有關(guān)的目標(biāo)文件彼此相連接,即將在一個文件中引用的符號同該符號在另外一個文件中的定義連接起來,使得所有的這些目標(biāo)文件成為一個能夠被操作系統(tǒng)裝入執(zhí)行的統(tǒng)一整體。
根據(jù)開發(fā)人員指定的同庫函數(shù)的鏈接方式的不同,鏈接處理分為兩種:
4.1 靜態(tài)鏈接
在這種鏈接方式下,函數(shù)的代碼將從其所在的靜態(tài)鏈接庫中被拷貝到最終的可執(zhí)行程序中。這樣該程序在被執(zhí)行時這些代碼將被裝入到該進(jìn)程的虛擬地址空間中。靜態(tài)鏈接庫實際上是一個目標(biāo)文件的集合,其中的每個文件含有庫中的一個或者一組相關(guān)函數(shù)的代碼。
4.2 動態(tài)鏈接
在此種方式下,函數(shù)的代碼被放到稱作是動態(tài)鏈接庫或共享對象的某個目標(biāo)文件中。鏈接程序此時所做的只是在最終的可執(zhí)行程序中記錄下共享對象的名字以及其它少量的登記信息。
在此可執(zhí)行文件被執(zhí)行時,動態(tài)鏈接庫的全部內(nèi)容將被映射到運行時相應(yīng)進(jìn)程的虛地址空間。動態(tài)鏈接程序?qū)⒏鶕?jù)可執(zhí)行程序中記錄的信息找到相應(yīng)的函數(shù)代碼。
對于可執(zhí)行文件中的函數(shù)調(diào)用,可分別采用動態(tài)鏈接或靜態(tài)鏈接的方法。使用動態(tài)鏈接能夠使最終的可執(zhí)行文件比較短小,并且當(dāng)共享對象被多個進(jìn)程使用時能節(jié)約一些內(nèi)存,因為在內(nèi)存中只需要保存一份此共享對象的代碼。但并不是使用動態(tài)鏈接就一定比使用靜態(tài)鏈接要優(yōu)越。在某些情況下動態(tài)鏈接可能帶來一些性能上損害。
-
嵌入式
+關(guān)注
關(guān)注
5082文章
19126瀏覽量
305291 -
ecu
+關(guān)注
關(guān)注
14文章
886瀏覽量
54508 -
C++
+關(guān)注
關(guān)注
22文章
2108瀏覽量
73655 -
編譯
+關(guān)注
關(guān)注
0文章
657瀏覽量
32873
發(fā)布評論請先 登錄
相關(guān)推薦
評論