日本黄色一级经典视频|伊人久久精品视频|亚洲黄色色周成人视频九九九|av免费网址黄色小短片|黄色Av无码亚洲成年人|亚洲1区2区3区无码|真人黄片免费观看|无码一级小说欧美日免费三级|日韩中文字幕91在线看|精品久久久无码中文字幕边打电话

當前位置:首頁 > > 充電吧
[導(dǎo)讀]人人都能讀懂的編譯器原理

簡單介紹

編譯器是什么?

你口中所說的編程語言本質(zhì)上只是一個軟件,這個軟件叫做編譯器,編譯器讀入一個文本文件,經(jīng)過大量的處理,最終產(chǎn)生一個二進制文件。?編譯器的語言部分就是它處理的文本樣式。因為電腦只能讀取 1 和 0 ,而人們編寫 Rust 程序要比直接編寫二進制程序簡單地多,因此編譯器就被用來把人類可讀的文本轉(zhuǎn)換成計算機可識別的機器碼。

編譯器可以是任何可以把文本文件轉(zhuǎn)換成其他文件的程序。例如,下面有一個用 Rust 語言寫的編譯器把 0 轉(zhuǎn)換成 1,把 1 轉(zhuǎn)換成 0 :


1

2

3

4

5

6

7

8

9

10

11

12

13

14

// An example compiler that turns 0s into 1s, and 1s into 0s.

?

fn main() {

????let input = "1 0 1 A 1 0 1 3";

?

????// iterate over every character `c` in input

????let output: String = input.chars().map(|c|

????????if c == '1' { '0' }

????????else if c == '0' { '1' }

????????else { c } // if not 0 or 1, leave it alone

????).collect();

?

????println!("{}", output); // 0 1 0 A 0 1 0 3

}


編譯器是做什么的?

簡言之,編譯器獲取源代碼,產(chǎn)生一個二進制文件。因為從復(fù)雜的、人類可讀的代碼直接轉(zhuǎn)化成0/1二進制會很復(fù)雜,所以編譯器在產(chǎn)生可運行程序之前有多個步驟:

  1. 從你給定的源代碼中讀取單個詞。

  2. 把這些詞按照單詞、數(shù)字、符號、運算符進行分類。

  3. 通過模式匹配從分好類的單詞中找出運算符,明確這些運算符想進行的運算,然后產(chǎn)生一個運算符的樹(表達式樹)。

  4. 最后一步遍歷表達式樹中的所有運算符,產(chǎn)生相應(yīng)的二進制數(shù)據(jù)。

盡管我說編譯器直接從表達式樹轉(zhuǎn)換到二進制,但實際上它會產(chǎn)生匯編代碼,之后匯編代碼會被匯編/編譯到二進制數(shù)據(jù)。匯編程序就好比是一種高級的、人類可讀的二進制。

解釋器是什么?

解釋器?非常像編譯器,它也是讀入編程語言的代碼,然后處理這些代碼。盡管如此,解釋器會跳過了代碼生成,然后即時編譯并執(zhí)行 AST。?解釋器最大的優(yōu)點就在于在你 debug 期間運行程序所消耗的時間。編譯器編譯一個程序可能在一秒到幾分鐘不等,然而解釋器可以立即開始執(zhí)行程序,而不必編譯。解釋器最大的缺點在于它必須安裝在用戶電腦上,程序才可以執(zhí)行。

雖然這篇文章主要是關(guān)于編譯器的,但是對于編譯器和解釋器之間的區(qū)別和編譯器相關(guān)的內(nèi)容一定要弄清楚。

1. 詞法分析

第一步是把輸入一個詞一個詞的拆分開。這一步被叫做?詞法分析,或者說是分詞。這一步的關(guān)鍵就在于?我們把字符組合成我們需要的單詞、標識符、符號等等。?詞法分析大多都不需要處理邏輯運算像是算出?2+2?– 其實這個表達式只有三種?標記:一個數(shù)字:2,一個加號,另外一個數(shù)字:2。

讓我們假設(shè)你正在解析一個像是?12+3?這樣的字符串:它會讀入字符?1,2,+,和?3。我們已經(jīng)把這些字符拆分開了,但是現(xiàn)在我們必須把他們組合起來;這是分詞器的主要任務(wù)之一。舉個例子,我們得到了兩個單獨的字符?1?和?2,但是我們需要把它們放到一起,然后把它們解析成為一個整數(shù)。至于?+也需要被識別為加號,而不是它的字符值 – 字符值是43 。

如果你可以閱讀過上面的代碼,并且弄懂了這樣做的含義,接下來的 Rust 分詞器會組合數(shù)字為32位整數(shù),加號就最后了標記值 Plus(加).

rust playground

你可以點擊 Rust playgroud 左上角的 “Run” 按鈕來編譯和執(zhí)行你瀏覽器中的代碼。

在一種編程語言的編譯器中,詞法解析器可能需要許多不同類型的標記。例如:符號,數(shù)字,標識符,字符串,操作符等。想知道要從源文件中提取怎樣的標記完全取決于編程語言本身。


1

2

3

4

5

6

7

8

9

10

int main() {

????int a;

????int b;

????a = b = 4;

????return a - b;

}

?

Scanner production:

[Keyword(Int), Id("main"), Symbol(LParen), Symbol(RParen), Symbol(LBrace), Keyword(Int), Id("a"), Symbol(Semicolon), Keyword(Int), Id("b"), Symbol(Semicolon), Id("a"), Operator(Assignment), Id("b"),

Operator(Assignment), Integer(4), Symbol(Semicolon), Keyword(Return), Id("a"), Operator(Minus), Id("b"), Symbol(Semicolon), Symbol(RBrace)]


C 語言的樣例代碼已經(jīng)進行過詞法分析,并且輸出了它的標記。


2. 解析

解析器確實是語法解析的核心。解析器提取由詞法分析器產(chǎn)生的標記,并嘗試判斷它們是否符合特定的模式,然后把這些模式與函數(shù)調(diào)用,變量調(diào)用,數(shù)學(xué)運算之類的表達式關(guān)聯(lián)起來。?解析器逐詞地定義編程語言的語法。

int a = 3?和?a: int = 3?的區(qū)別在于解析器的處理上面。解析器決定了語法的外在形式是怎樣的。它確保括號和花括號的左右括號是數(shù)量平衡的,每個語句結(jié)尾都有一個分號,每個函數(shù)都有一個名稱。當標記不符合預(yù)期的模式時,解析器就會知道標記的順序不正確。

你可以寫好幾種不同類型的解析器。最常見的解析器之一是從上到下的,遞歸降解的解析器。遞歸降解的解析器是用起來最簡單也是最容易理解的解析器。我寫的所有解析器樣例都是基于遞歸降解的。

解析器解析的語法可以使用一種?語法?表示出來。像?EBNF?這樣的語法就可以描述一個解析器用于解析簡單的數(shù)學(xué)運算,像是這樣?12+3?:


1

2

3

expr = additive_expr ;

additive_expr = term, ('+' | '-'), term ;

term = number ;


簡單加法和減法表達式的 EBNF 語法。

請記住語法文件并不是解析器,但是它確實是解析器的一種表達形式。你可以圍繞上面的語法創(chuàng)建一個解析器。語法文件可以被人使用并且比起直接閱讀和理解解析器的代碼要簡單許多。


那種語法的解析器應(yīng)該是?expr?解析器,因為它直接與所有內(nèi)容都相關(guān)的頂層。唯一有效的輸入必須是任意數(shù)字,加號或減號,任意數(shù)字。expr?需要一個?additive_expr,這主要出現(xiàn)在加法和減法表達式中。additive_expr?首先需要一個?term?(一個數(shù)字),然后是加號或者減號,最后是另一個?term?。

?



解析 12+3 產(chǎn)生的樣例 AST

解析器在解析時產(chǎn)生的樹狀結(jié)構(gòu)被稱為?抽象的語法樹,或者稱之為 AST。?ast 中包含了所有要進行操作。解析器不會計算這些操作,它只是以正確的順序來收集其中的標記。


我之前補充了我們的詞法分析器代碼,以便它與我們的語法想匹配,并且可以產(chǎn)生像圖表一樣的 AST。我用?// BEGIN PARSER //?和?// END PARSER //?的注釋標記出了新的解析器代碼的開頭和結(jié)尾。

rust playground

我們可以再深入一點。假設(shè)我們想要支持只有數(shù)字沒有運算符的輸入,或者添加除法和乘法,甚至添加優(yōu)先級。只要簡單地修改一下語法文件,這些都是完全有可能的,任何調(diào)整都會直接反映在我們的解析器代碼中。


1

2

3

4

expr = additive_expr ;

additive_expr = multiplicative_expr, { ('+' | '-'), multiplicative_expr } ;

multiplicative_expr = term, { ("*" | "/"), term } ;

term = number ;


新的語法。

https://play.rust-lang.org/?gist=1587a5dd6109f70cafe68818a8c1a883&version=nightly&mode=debug&edition=2018


?

針對 C 語言語法編寫的解析器(又叫做詞法分析器)和解析器樣例。從字符序列的開始 “if(net>0.0)total+=net(1.0+tax/100.0);”,掃描器組成了一系列標記,并且對它們進行分類,例如,標識符,保留字,數(shù)字,或者運算符。后者的序列由解析器轉(zhuǎn)換成語法樹,然后由其他的編譯器分階段進行處理。掃描器和解析器分別處理 C 語法中的規(guī)則和與上下文無關(guān)的部分。

3. 生成代碼

代碼生成器?接收一個 AST ,然后生成相應(yīng)的代碼或者匯編代碼。代碼生成器必須以遞歸下降的順序遍歷AST中的所有內(nèi)容-就像是解析器的工作方式一樣-之后生成相應(yīng)的內(nèi)容,只不過這里生成的不再是語法樹,而是代碼了。

https://godbolt.org/z/K8416_

如果打開上面的鏈接,你就可以看到左側(cè)樣例代碼產(chǎn)生的匯編代碼。匯編代碼的第三行和第四行展示了編譯器在AST中遇到常量的時候是怎樣為這些常量生成相應(yīng)的代碼的。

Godbolt Compiler Explorer 是一個很棒的工具,允許你用高級語言編寫代碼,并查看它產(chǎn)生的匯編代碼。你可以有點暈頭轉(zhuǎn)向了,想知道產(chǎn)生的是哪種代碼,但不要忘記給你的編程語言編譯器添加優(yōu)化選項來看看它到底有多智能。(對于 Rust 是?-O?)

如果你對于編譯器是在匯編語言中怎樣把一個本地變量保存到內(nèi)存中感興趣的話,這篇文章(“代碼生成”部分)非常詳細地解釋了堆棧的相關(guān)知識。大多數(shù)情況下,當變量不是本地變量的時候,高級編譯器會在堆區(qū)為變量分配空間,并把它們保存到堆區(qū),而不是棧區(qū)。你可以從這個 StackOverflow 的回答上閱讀更多關(guān)于變量存儲的內(nèi)容。

因為匯編是一個完全不同的,而且復(fù)雜的主題,因此這里我不會過多地討論它。我只是想強調(diào)代碼生成器的重要性和它的作用。此外,代碼生成器不僅可以產(chǎn)生匯編代碼。Haxe?編譯器有一個可以產(chǎn)生 6 種以上不同的編程語言的后端:包括 C++,Java,和 Python。

后端指的是編譯器的代碼生成器或者表達式解析器;因此前端是詞法分析器和解析器。同樣也有一個中間端,它通常與優(yōu)化和 IR 有關(guān),這部分會在稍后解釋。后端通常與前端無關(guān),后端只關(guān)心它接收到的 AST。這意味著可以為幾種不同的前端或者語言重用相同的后端。大名鼎鼎的?GNU Compiler Collection?就屬于這種情況。

我找不到比我的 C 編譯器后端更好的代碼生成器示例了;

在生成匯編代碼之后,這些匯編代碼會被寫入到一個新的匯編文件中 (.s?或?.asm)。然后該文件會被傳遞給匯編器,匯編器是匯編語言的編譯器,它會生成相應(yīng)的二進制代碼。之后這些二進制代碼會被寫入到一個新的目標文件中 (.o) 。

目標文件是機器碼,但是它們并不可以被執(zhí)行。?為了讓它們變成可執(zhí)行文件,目標文件需要被鏈接到一起。鏈接器讀取通用的機器碼,然后使它變?yōu)橐粋€可執(zhí)行文件、共享庫或是?靜態(tài)庫。

鏈接器是因操作系統(tǒng)而不同的應(yīng)用程序。隨便一個第三方的鏈接器都應(yīng)該可以編譯你后端產(chǎn)生的目標代碼。因此在寫編譯器的時候不需要創(chuàng)建你自己的鏈接器。

編譯器可能有?中間表示,或者簡稱 IR 。IR 主要是為了在優(yōu)化或者翻譯成另一門語言的時候,無損地表示原來的指令。?IR 不再是原來的代碼;IR 是為了尋找代碼中潛在的優(yōu)化而進行的無損簡化。循環(huán)展開?和?向量化?都是利用 IR 完成的。更多關(guān)于 IR 相關(guān)的優(yōu)化可以在這個?PDF?中找到。


本站聲明: 本文章由作者或相關(guān)機構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內(nèi)容真實性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時聯(lián)系本站刪除。
換一批
延伸閱讀

LED驅(qū)動電源的輸入包括高壓工頻交流(即市電)、低壓直流、高壓直流、低壓高頻交流(如電子變壓器的輸出)等。

關(guān)鍵字: 驅(qū)動電源

在工業(yè)自動化蓬勃發(fā)展的當下,工業(yè)電機作為核心動力設(shè)備,其驅(qū)動電源的性能直接關(guān)系到整個系統(tǒng)的穩(wěn)定性和可靠性。其中,反電動勢抑制與過流保護是驅(qū)動電源設(shè)計中至關(guān)重要的兩個環(huán)節(jié),集成化方案的設(shè)計成為提升電機驅(qū)動性能的關(guān)鍵。

關(guān)鍵字: 工業(yè)電機 驅(qū)動電源

LED 驅(qū)動電源作為 LED 照明系統(tǒng)的 “心臟”,其穩(wěn)定性直接決定了整個照明設(shè)備的使用壽命。然而,在實際應(yīng)用中,LED 驅(qū)動電源易損壞的問題卻十分常見,不僅增加了維護成本,還影響了用戶體驗。要解決這一問題,需從設(shè)計、生...

關(guān)鍵字: 驅(qū)動電源 照明系統(tǒng) 散熱

根據(jù)LED驅(qū)動電源的公式,電感內(nèi)電流波動大小和電感值成反比,輸出紋波和輸出電容值成反比。所以加大電感值和輸出電容值可以減小紋波。

關(guān)鍵字: LED 設(shè)計 驅(qū)動電源

電動汽車(EV)作為新能源汽車的重要代表,正逐漸成為全球汽車產(chǎn)業(yè)的重要發(fā)展方向。電動汽車的核心技術(shù)之一是電機驅(qū)動控制系統(tǒng),而絕緣柵雙極型晶體管(IGBT)作為電機驅(qū)動系統(tǒng)中的關(guān)鍵元件,其性能直接影響到電動汽車的動力性能和...

關(guān)鍵字: 電動汽車 新能源 驅(qū)動電源

在現(xiàn)代城市建設(shè)中,街道及停車場照明作為基礎(chǔ)設(shè)施的重要組成部分,其質(zhì)量和效率直接關(guān)系到城市的公共安全、居民生活質(zhì)量和能源利用效率。隨著科技的進步,高亮度白光發(fā)光二極管(LED)因其獨特的優(yōu)勢逐漸取代傳統(tǒng)光源,成為大功率區(qū)域...

關(guān)鍵字: 發(fā)光二極管 驅(qū)動電源 LED

LED通用照明設(shè)計工程師會遇到許多挑戰(zhàn),如功率密度、功率因數(shù)校正(PFC)、空間受限和可靠性等。

關(guān)鍵字: LED 驅(qū)動電源 功率因數(shù)校正

在LED照明技術(shù)日益普及的今天,LED驅(qū)動電源的電磁干擾(EMI)問題成為了一個不可忽視的挑戰(zhàn)。電磁干擾不僅會影響LED燈具的正常工作,還可能對周圍電子設(shè)備造成不利影響,甚至引發(fā)系統(tǒng)故障。因此,采取有效的硬件措施來解決L...

關(guān)鍵字: LED照明技術(shù) 電磁干擾 驅(qū)動電源

開關(guān)電源具有效率高的特性,而且開關(guān)電源的變壓器體積比串聯(lián)穩(wěn)壓型電源的要小得多,電源電路比較整潔,整機重量也有所下降,所以,現(xiàn)在的LED驅(qū)動電源

關(guān)鍵字: LED 驅(qū)動電源 開關(guān)電源

LED驅(qū)動電源是把電源供應(yīng)轉(zhuǎn)換為特定的電壓電流以驅(qū)動LED發(fā)光的電壓轉(zhuǎn)換器,通常情況下:LED驅(qū)動電源的輸入包括高壓工頻交流(即市電)、低壓直流、高壓直流、低壓高頻交流(如電子變壓器的輸出)等。

關(guān)鍵字: LED 隧道燈 驅(qū)動電源
關(guān)閉