溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

go語(yǔ)言需要編譯嗎

發(fā)布時(shí)間:2022-12-02 09:40:23 來(lái)源:億速云 閱讀:134 作者:iii 欄目:編程語(yǔ)言

這篇文章主要介紹“go語(yǔ)言需要編譯嗎”,在日常操作中,相信很多人在go語(yǔ)言需要編譯嗎問(wèn)題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”go語(yǔ)言需要編譯嗎”的疑惑有所幫助!接下來(lái),請(qǐng)跟著小編一起來(lái)學(xué)習(xí)吧!

go語(yǔ)言需要編譯。Go語(yǔ)言是編譯型的靜態(tài)語(yǔ)言,是一門(mén)需要編譯才能運(yùn)行的編程語(yǔ)言,也就說(shuō)Go語(yǔ)言程序在運(yùn)行之前需要通過(guò)編譯器生成二進(jìn)制機(jī)器碼(二進(jìn)制的可執(zhí)行文件),隨后二進(jìn)制文件才能在目標(biāo)機(jī)器上運(yùn)行。

Go語(yǔ)言是一門(mén)需要編譯才能運(yùn)行的編程語(yǔ)言,也就說(shuō)代碼在運(yùn)行之前需要通過(guò)編譯器生成二進(jìn)制機(jī)器碼,隨后二進(jìn)制文件才能在目標(biāo)機(jī)器上運(yùn)行。

簡(jiǎn)單來(lái)說(shuō),Go語(yǔ)言是編譯型的靜態(tài)語(yǔ)言(和C語(yǔ)言一樣),所以在運(yùn)行Go語(yǔ)言程序之前,先要將其編譯成二進(jìn)制的可執(zhí)行文件。

如果我們想要了解Go語(yǔ)言的實(shí)現(xiàn)原理,理解它的編譯過(guò)程就是一個(gè)沒(méi)有辦法繞過(guò)的事情。下面就來(lái)看看Go語(yǔ)言是怎么完成編譯的。

預(yù)備知識(shí)

想要深入了解Go語(yǔ)言的編譯過(guò)程,需要提前了解一下編譯過(guò)程中涉及的一些術(shù)語(yǔ)和專業(yè)知識(shí)。這些知識(shí)其實(shí)在我們的日常工作和學(xué)習(xí)中比較難用到,但是對(duì)于理解編譯的過(guò)程和原理還是非常重要的。

1) 抽象語(yǔ)法樹(shù)

在計(jì)算機(jī)科學(xué)中,抽象語(yǔ)法樹(shù)(Abstract Syntax Tree,AST),或簡(jiǎn)稱語(yǔ)法樹(shù)(Syntax tree),是源代碼語(yǔ)法結(jié)構(gòu)的一種抽象表示。它以樹(shù)狀的形式表現(xiàn)編程語(yǔ)言的語(yǔ)法結(jié)構(gòu),樹(shù)上的每個(gè)節(jié)點(diǎn)都表示源代碼中的一種結(jié)構(gòu)。

之所以說(shuō)語(yǔ)法是“抽象”的,是因?yàn)檫@里的語(yǔ)法并不會(huì)表示出真實(shí)語(yǔ)法中出現(xiàn)的每個(gè)細(xì)節(jié)。比如,嵌套括號(hào)被隱含在樹(shù)的結(jié)構(gòu)中,并沒(méi)有以節(jié)點(diǎn)的形式呈現(xiàn)。而類似于 if else 這樣的條件判斷語(yǔ)句,可以使用帶有兩個(gè)分支的節(jié)點(diǎn)來(lái)表示。

以算術(shù)表達(dá)式 1+3*(4-1)+2 為例,可以解析出的抽象語(yǔ)法樹(shù)如下圖所示:

go語(yǔ)言需要編譯嗎

圖:抽象語(yǔ)法樹(shù)

抽象語(yǔ)法樹(shù)可以應(yīng)用在很多領(lǐng)域,比如瀏覽器,智能編輯器,編譯器。

2) 靜態(tài)單賦值

在編譯器設(shè)計(jì)中,靜態(tài)單賦值形式(static single assignment form,通常簡(jiǎn)寫(xiě)為 SSA form 或是 SSA)是中介碼(IR,intermediate representation)的屬性,它要求每個(gè)變量只分配一次,并且變量需要在使用之前定義。在實(shí)踐中我們通常會(huì)用添加下標(biāo)的方式實(shí)現(xiàn)每個(gè)變量只能被賦值一次的特性,這里以下面的代碼舉一個(gè)簡(jiǎn)單的例子:

x := 1
x := 2
y := x

從上面的描述所知,第一行賦值行為是不需要的,因?yàn)?x 在第二行被二度賦值并在第三行被使用,在 SSA 下,將會(huì)變成下列的形式:

x1 := 1
x2 := 2
y1 := x2

從使用 SSA 的中間代碼我們就可以非常清晰地看出變量 y1 的值和 x1 是完全沒(méi)有任何關(guān)系的,所以在機(jī)器碼生成時(shí)其實(shí)就可以省略第一步,這樣就能減少需要執(zhí)行的指令來(lái)優(yōu)化這一段代碼。

根據(jù) Wikipedia(維基百科)對(duì) SSA 的介紹來(lái)看,在中間代碼中使用 SSA 的特性能夠?yàn)檎麄€(gè)程序?qū)崿F(xiàn)以下的優(yōu)化:

  • 常數(shù)傳播(constant propagation)

  • 值域傳播(value range propagation)

  • 稀疏有條件的常數(shù)傳播(sparse conditional constant propagation)

  • 消除無(wú)用的程式碼(dead code elimination)

  • 全域數(shù)值編號(hào)(global value numbering)

  • 消除部分的冗余(partial redundancy elimination)

  • 強(qiáng)度折減(strength reduction)

  • 寄存器分配(register allocation)

因?yàn)?SSA 的作用的主要作用就是代碼的優(yōu)化,所以是編譯器后端(主要負(fù)責(zé)目標(biāo)代碼的優(yōu)化和生成)的一部分。當(dāng)然,除了 SSA 之外代碼編譯領(lǐng)域還有非常多的中間代碼優(yōu)化方法,優(yōu)化編譯器生成的代碼是一個(gè)非常古老并且復(fù)雜的領(lǐng)域,這里就不展開(kāi)介紹了。

3) 指令集架構(gòu)

最后要介紹的一個(gè)預(yù)備知識(shí)就是指令集架構(gòu)了,指令集架構(gòu)(Instruction Set Architecture,簡(jiǎn)稱 ISA),又稱指令集或指令集體系,是計(jì)算機(jī)體系結(jié)構(gòu)中與程序設(shè)計(jì)有關(guān)的部分,包含了基本數(shù)據(jù)類型,指令集,寄存器,尋址模式,存儲(chǔ)體系,中斷,異常處理以及外部 I/O。指令集架構(gòu)包含一系列的 opcode 即操作碼(機(jī)器語(yǔ)言),以及由特定處理器執(zhí)行的基本命令。

指令集架構(gòu)常見(jiàn)種類如下:

  • 復(fù)雜指令集運(yùn)算(Complex Instruction Set Computing,簡(jiǎn)稱 CISC);

  • 精簡(jiǎn)指令集運(yùn)算(Reduced Instruction Set Computing,簡(jiǎn)稱 RISC);

  • 顯式并行指令集運(yùn)算(Explicitly Parallel Instruction Computing,簡(jiǎn)稱 EPIC);

  • 超長(zhǎng)指令字指令集運(yùn)算(VLIW)。


不同的處理器(CPU)使用了大不相同的機(jī)器語(yǔ)言,所以我們的程序想要在不同的機(jī)器上運(yùn)行,就需要將源代碼根據(jù)架構(gòu)編譯成不同的機(jī)器語(yǔ)言。

編譯原理

Go語(yǔ)言編譯器的源代碼在 cmd/compile 目錄中,目錄下的文件共同構(gòu)成了Go語(yǔ)言的編譯器,學(xué)過(guò)編譯原理的人可能聽(tīng)說(shuō)過(guò)編譯器的前端和后端,編譯器的前端一般承擔(dān)著詞法分析、語(yǔ)法分析、類型檢查和中間代碼生成幾部分工作,而編譯器后端主要負(fù)責(zé)目標(biāo)代碼的生成和優(yōu)化,也就是將中間代碼翻譯成目標(biāo)機(jī)器能夠運(yùn)行的機(jī)器碼。

go語(yǔ)言需要編譯嗎

Go的編譯器在邏輯上可以被分成四個(gè)階段:詞法與語(yǔ)法分析、類型檢查和 AST 轉(zhuǎn)換、通用 SSA 生成和最后的機(jī)器代碼生成,下面我們來(lái)分別介紹一下這四個(gè)階段做的工作。

1) 詞法與語(yǔ)法分析

所有的編譯過(guò)程其實(shí)都是從解析代碼的源文件開(kāi)始的,詞法分析的作用就是解析源代碼文件,它將文件中的字符串序列轉(zhuǎn)換成 Token 序列,方便后面的處理和解析,我們一般會(huì)把執(zhí)行詞法分析的程序稱為詞法解析器(lexer)。

而語(yǔ)法分析的輸入就是詞法分析器輸出的 Token 序列,這些序列會(huì)按照順序被語(yǔ)法分析器進(jìn)行解析,語(yǔ)法的解析過(guò)程就是將詞法分析生成的 Token 按照語(yǔ)言定義好的文法(Grammar)自下而上或者自上而下的進(jìn)行規(guī)約,每一個(gè) Go 的源代碼文件最終會(huì)被歸納成一個(gè) SourceFile 結(jié)構(gòu):

SourceFile = PackageClause ";" { ImportDecl ";" } { TopLevelDecl ";" }

標(biāo)準(zhǔn)的 Golang 語(yǔ)法解析器使用的就是 LALR(1) 的文法,語(yǔ)法解析的結(jié)果其實(shí)就是上面介紹過(guò)的抽象語(yǔ)法樹(shù)(AST),每一個(gè) AST 都對(duì)應(yīng)著一個(gè)單獨(dú)的Go語(yǔ)言文件,這個(gè)抽象語(yǔ)法樹(shù)中包括當(dāng)前文件屬于的包名、定義的常量、結(jié)構(gòu)體和函數(shù)等。

如果在語(yǔ)法解析的過(guò)程中發(fā)生了任何語(yǔ)法錯(cuò)誤,都會(huì)被語(yǔ)法解析器發(fā)現(xiàn)并將消息打印到標(biāo)準(zhǔn)輸出上,整個(gè)編譯過(guò)程也會(huì)隨著錯(cuò)誤的出現(xiàn)而被中止。

2) 類型檢查

當(dāng)拿到一組文件的抽象語(yǔ)法樹(shù) AST 之后,Go語(yǔ)言的編譯器會(huì)對(duì)語(yǔ)法樹(shù)中定義和使用的類型進(jìn)行檢查,類型檢查分別會(huì)按照順序?qū)Σ煌愋偷墓?jié)點(diǎn)進(jìn)行驗(yàn)證,按照以下的順序進(jìn)行處理:

  • 常量、類型和函數(shù)名及類型;

  • 變量的賦值和初始化;

  • 函數(shù)和閉包的主體;

  • 哈希鍵值對(duì)的類型;

  • 導(dǎo)入函數(shù)體;

  • 外部的聲明;

通過(guò)對(duì)每一棵抽象節(jié)點(diǎn)樹(shù)的遍歷,我們?cè)诿恳粋€(gè)節(jié)點(diǎn)上都會(huì)對(duì)當(dāng)前子樹(shù)的類型進(jìn)行驗(yàn)證保證當(dāng)前節(jié)點(diǎn)上不會(huì)出現(xiàn)類型錯(cuò)誤的問(wèn)題,所有的類型錯(cuò)誤和不匹配都會(huì)在這一個(gè)階段被發(fā)現(xiàn)和暴露出來(lái)。

類型檢查的階段不止會(huì)對(duì)樹(shù)狀結(jié)構(gòu)的節(jié)點(diǎn)進(jìn)行驗(yàn)證,同時(shí)也會(huì)對(duì)一些內(nèi)建的函數(shù)進(jìn)行展開(kāi)和改寫(xiě),例如 make 關(guān)鍵字在這個(gè)階段會(huì)根據(jù)子樹(shù)的結(jié)構(gòu)被替換成 makeslice 或者 makechan 等函數(shù)。

其實(shí)類型檢查不止對(duì)類型進(jìn)行了驗(yàn)證工作,還對(duì) AST 進(jìn)行了改寫(xiě)以及處理Go語(yǔ)言內(nèi)置的關(guān)鍵字,所以,這一過(guò)程在整個(gè)編譯流程中是非常重要的,沒(méi)有這個(gè)步驟很多關(guān)鍵字其實(shí)就沒(méi)有辦法工作?!鞠嚓P(guān)推薦:Go視頻教程】

3) 中間代碼生成

當(dāng)我們將源文件轉(zhuǎn)換成了抽象語(yǔ)法樹(shù),對(duì)整個(gè)語(yǔ)法樹(shù)的語(yǔ)法進(jìn)行解析并進(jìn)行類型檢查之后,就可以認(rèn)為當(dāng)前文件中的代碼基本上不存在無(wú)法編譯或者語(yǔ)法錯(cuò)誤的問(wèn)題了,Go語(yǔ)言的編譯器就會(huì)將輸入的 AST 轉(zhuǎn)換成中間代碼。

Go語(yǔ)言編譯器的中間代碼使用了 SSA(Static Single Assignment Form) 的特性,如果我們?cè)谥虚g代碼生成的過(guò)程中使用這種特性,就能夠比較容易的分析出代碼中的無(wú)用變量和片段并對(duì)代碼進(jìn)行優(yōu)化。

在類型檢查之后,就會(huì)通過(guò)一個(gè)名為 compileFunctions 的函數(shù)開(kāi)始對(duì)整個(gè)Go語(yǔ)言項(xiàng)目中的全部函數(shù)進(jìn)行編譯,這些函數(shù)會(huì)在一個(gè)編譯隊(duì)列中等待幾個(gè)后端工作協(xié)程的消費(fèi),這些 Goroutine 會(huì)將所有函數(shù)對(duì)應(yīng)的 AST 轉(zhuǎn)換成使用 SSA 特性的中間代碼。

4) 機(jī)器碼生成

Go語(yǔ)言源代碼的 cmd/compile/internal 目錄中包含了非常多機(jī)器碼生成相關(guān)的包,不同類型的 CPU 分別使用了不同的包進(jìn)行生成 amd64、arm、arm64、mips、mips64、ppc64、s390x、x86 和 wasm,也就是說(shuō)Go語(yǔ)言能夠在幾乎全部常見(jiàn)的 CPU 指令集類型上運(yùn)行。

編譯器入口

Go語(yǔ)言的編譯器入口是 src/cmd/compile/internal/gc 包中的 main.go 文件,這個(gè) 600 多行的 Main 函數(shù)就是Go語(yǔ)言編譯器的主程序,這個(gè)函數(shù)會(huì)先獲取命令行傳入的參數(shù)并更新編譯的選項(xiàng)和配置,隨后就會(huì)開(kāi)始運(yùn)行 parseFiles 函數(shù)對(duì)輸入的所有文件進(jìn)行詞法與語(yǔ)法分析得到文件對(duì)應(yīng)的抽象語(yǔ)法樹(shù):

func Main(archInit func(*Arch)) {
    // ...
    lines := parseFiles(flag.Args())

接下來(lái)就會(huì)分九個(gè)階段對(duì)抽象語(yǔ)法樹(shù)進(jìn)行更新和編譯,就像我們?cè)谏厦娼榻B的,整個(gè)過(guò)程會(huì)經(jīng)歷類型檢查、SSA 中間代碼生成以及機(jī)器碼生成三個(gè)部分:

  • 檢查常量、類型和函數(shù)的類型;

  • 處理變量的賦值;

  • 對(duì)函數(shù)的主體進(jìn)行類型檢查;

  • 決定如何捕獲變量;

  • 檢查內(nèi)聯(lián)函數(shù)的類型;

  • 進(jìn)行逃逸分析;

  • 將閉包的主體轉(zhuǎn)換成引用的捕獲變量;

  • 編譯頂層函數(shù);

  • 檢查外部依賴的聲明;

了解了剩下的編譯過(guò)程之后,我們重新回到詞法和語(yǔ)法分析后的具體流程,在這里編譯器會(huì)對(duì)生成語(yǔ)法樹(shù)中的節(jié)點(diǎn)執(zhí)行類型檢查,除了常量、類型和函數(shù)這些頂層聲明之外,它還會(huì)對(duì)變量的賦值語(yǔ)句、函數(shù)主體等結(jié)構(gòu)進(jìn)行檢查:

for i := 0; i < len(xtop); i++ {
    n := xtop[i]
    if op := n.Op; op != ODCL && op != OAS && op != OAS2 && (op != ODCLTYPE || !n.Left.Name.Param.Alias) {
        xtop[i] = typecheck(n, ctxStmt)
    }
}

for i := 0; i < len(xtop); i++ {
    n := xtop[i]
    if op := n.Op; op == ODCL || op == OAS || op == OAS2 || op == ODCLTYPE && n.Left.Name.Param.Alias {
        xtop[i] = typecheck(n, ctxStmt)
    }
}

for i := 0; i < len(xtop); i++ {
    n := xtop[i]
    if op := n.Op; op == ODCLFUNC || op == OCLOSURE {
        typecheckslice(Curfn.Nbody.Slice(), ctxStmt)
    }
}

checkMapKeys()

for _, n := range xtop {
    if n.Op == ODCLFUNC && n.Func.Closure != nil {
        capturevars(n)
    }
}

escapes(xtop)

for _, n := range xtop {
    if n.Op == ODCLFUNC && n.Func.Closure != nil {
        transformclosure(n)
    }
}

類型檢查會(huì)對(duì)傳入節(jié)點(diǎn)的子節(jié)點(diǎn)進(jìn)行遍歷,這個(gè)過(guò)程會(huì)對(duì) make 等關(guān)鍵字進(jìn)行展開(kāi)和重寫(xiě),類型檢查結(jié)束之后并沒(méi)有輸出新的數(shù)據(jù)結(jié)構(gòu),只是改變了語(yǔ)法樹(shù)中的一些節(jié)點(diǎn),同時(shí)這個(gè)過(guò)程的結(jié)束也意味著源代碼中已經(jīng)不存在語(yǔ)法錯(cuò)誤和類型錯(cuò)誤,中間代碼和機(jī)器碼也都可以正常的生成了。

    initssaconfig()

    peekitabs()

    for i := 0; i < len(xtop); i++ {
        n := xtop[i]
        if n.Op == ODCLFUNC {
            funccompile(n)
        }
    }

    compileFunctions()

    for i, n := range externdcl {
        if n.Op == ONAME {
            externdcl[i] = typecheck(externdcl[i], ctxExpr)
        }
    }

    checkMapKeys()
}

在主程序運(yùn)行的最后,會(huì)將頂層的函數(shù)編譯成中間代碼并根據(jù)目標(biāo)的 CPU 架構(gòu)生成機(jī)器碼,不過(guò)這里其實(shí)也可能會(huì)再次對(duì)外部依賴進(jìn)行類型檢查以驗(yàn)證正確性。

到此,關(guān)于“go語(yǔ)言需要編譯嗎”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)?lái)更多實(shí)用的文章!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI