欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入了解Go語言編譯鏈接的過程

 更新時間:2023年08月01日 14:26:44   作者:小許code  
Go在編譯時會將interface和channel關(guān)鍵字轉(zhuǎn)換成runtime中的結(jié)構(gòu)和函數(shù)調(diào)用,所以小編覺得很有必要就Go的編譯過程理一理做個進(jìn)行總結(jié),下面就來和小編一起了解一下Go語言編譯鏈接的過程吧

1 前言

interface、channel的文章中經(jīng)常會提到,Go在編譯時會將interface和channel關(guān)鍵字轉(zhuǎn)換成runtime中的結(jié)構(gòu)和函數(shù)調(diào)用。所以我覺得很有必要就Go的編譯過程理一理做個進(jìn)行總結(jié),然后結(jié)合之前對底層原理總結(jié)的文章,那么對整個邏輯會更加清晰。我也是查了各種資料,盡量把整個過程能總起出一些東西來,學(xué)習(xí)嘛,總是需要不斷總結(jié),分享!

1.1 什么是ASCII字符

ASCII 代表美國信息交換標(biāo)準(zhǔn)代碼,用于電子通信。在計(jì)算機(jī)內(nèi)部,所有信息最終都是一個二進(jìn)制值。每一個二進(jìn)制位(bit)有0和1兩種狀態(tài),因此八個二進(jìn)制位就可以組合出256種狀態(tài),這被稱為一個字節(jié)(byte)。也就是說,一個字節(jié)一共可以用來表示256種不同的狀態(tài),每一個狀態(tài)對應(yīng)一個符號,就是256個符號,從00000000到11111111。 上個世紀(jì)60年代,美國制定了一套字符編碼,對英語字符與二進(jìn)制位之間的關(guān)系,做了統(tǒng)一規(guī)定,這被稱為 ASCII 碼,一直沿用至今。

它使用整數(shù)對數(shù)字(0-9)、大寫字母(AZ)、小寫字母(az)和分號(;)、感嘆號(?。┑确栠M(jìn)行編碼。整數(shù)比字母或字母更容易存儲在電子設(shè)備中符號。例如,97用于表示“a”,33用于表示“!” 并且可以很容易地存儲在內(nèi)存中。

我們知道Go是采用UTF-8是編碼規(guī)則, 和ASCII碼之間的聯(lián)系呢?了解UTF-8之前我們先了解Unicode,因?yàn)锳SCII碼只能表示英語,不能表示其他語言。Unicode 為世界上所有字符都分配了一個唯一的數(shù)字編號,但是Unicode 只是一個符號集,它只規(guī)定了符號的二進(jìn)制代碼,卻沒有規(guī)定這個二進(jìn)制代碼應(yīng)該如何存儲。

因?yàn)榛ヂ?lián)網(wǎng)的普及,UTF-8 就是在互聯(lián)網(wǎng)上使用最廣的一種 Unicode 的實(shí)現(xiàn)方式。Unicode的編碼規(guī)則,對于單字節(jié)的符號,字節(jié)的第一位設(shè)為0,后面7位為這個符號的 Unicode 碼,因此對于英語字母,UTF-8 編碼和 ASCII 碼是相同的。

對于ASCII碼、Unicode、UTF-8之間的聯(lián)系就不展開更多更細(xì)的總結(jié),用個栗子來說明下我們編寫的Go程序文件和編碼之間關(guān)系。

1.2 圖說ASCII碼和Go程序文件

這里參考下一個網(wǎng)絡(luò)上的圖片說明,首先我們Go代碼Hello.go如下

package main
import "fmt"
func main() {
 fmt.Println("hello world")
}

我們知道我們用心敲下的每一行代碼都是字節(jié)序列,然后每個字節(jié)代表一個字符,代碼和ASCII碼之間的對應(yīng)關(guān)系就是中間一列代表文本對應(yīng)的 ASCII 字符,最右邊的列就是我們的代碼,跟下面的對照表示一一對應(yīng)的,比如hello.go文件的首字母p的值是對應(yīng)的就是70。

16進(jìn)制查看文件內(nèi)容

ASCII碼對照表

hello.go 文件都是由 ASCII 字符表示的,它被稱為文本文件,8個bit看成一個單位,假定源程序都是ASCII碼,轉(zhuǎn)換為我們?nèi)祟惗寄芨美斫獾膅o程序,那么到這里編碼和程序文件之間的關(guān)系已經(jīng)清楚了,接下來就從編譯和鏈接過程來看有哪些步驟,然后每一個步驟做了什么!

2 編譯過程

我們知道Go 程序并不能直接運(yùn)行,每條 Go 語句必須轉(zhuǎn)化為一系列的低級機(jī)器語言指令,將這些指令打包到一起,并以二進(jìn)制磁盤文件的形式存儲起來,也就是可執(zhí)行目標(biāo)文件。這個過程就涉及到對源文件進(jìn)行詞法分析、語法分析、語義分析、優(yōu)化,最后生成匯編代碼文件(以.s作為文件后綴),再經(jīng)過匯編器將匯編文件生成.o二進(jìn)制程序,最后經(jīng)過鏈接器轉(zhuǎn)換成可執(zhí)行的目標(biāo)程序(比如windows下的.exe程序)。

源文件編譯為執(zhí)行程序的過程

編譯過程

2.1 詞法分析

詞法分析(lexical analysis)維基百科上給出的定義:是計(jì)算機(jī)科學(xué)中將字符序列轉(zhuǎn)換為標(biāo)記(token)序列的過程。進(jìn)行詞法分析的程序或者函數(shù)叫作詞法分析器(lexical analyzer,簡稱lexer),也叫掃描器(scanner)。詞法分析器一般以函數(shù)的形式存在,供語法分析器調(diào)。

Go在編譯源碼時首先,由詞法分析器(lexer)對源代碼文件進(jìn)行解析,將文件中的字符串序列轉(zhuǎn)為Token序列(在src/cmd/compile/internal/syntax/tokens.go),token包含標(biāo)識符、關(guān)鍵字、特殊符號等都是以常量的形式存在。

const (
 _    token = iota
 _EOF       // EOF
 // names and literals
 _Name    // name
 _Literal // literal
 // operators and operations
 // _Operator is excluding '*' (_Star)
 _Operator // op
 _AssignOp // op=
 _IncOp    // opop
 _Assign   // =
 _Define   // :=
 _Arrow    // <-
 _Star     // *

而掃描代碼在(src/cmd/compile/internal/syntax/scanner.go),通過核心的next()函數(shù),不斷讀取下一個函數(shù),然后通過一個大的switch-case來匹配比如換行、字符串、括號等標(biāo)識符將其轉(zhuǎn)換為token,從而完成一次解析。

func (s *scanner) next() {
 for s.ch == ' ' || s.ch == '\t' || s.ch == '\n' && !nlsemi || s.ch == '\r' {
  s.nextch()
 }
 ...
 switch s.ch {
 case -1:
  if nlsemi {
   s.lit = "EOF"
   s.tok = _Semi
   break
  }
  s.tok = _EOF
 case '\n':
  s.nextch()
  s.lit = "newline"
  s.tok = _Semi
 case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
  s.number(false)
 case '"':
  s.stdString()
 case '`':
  s.rawString()
 ...
}

2.2 語法分析

語法分析的輸入是詞法分析器輸出的 Token 序列,語法分析器會按照順序解析 Token 序列,該過程會將詞法分析生成的 Token 按照編程語言定義好的文法(Grammar)自下而上或者自上而下的規(guī)約,

轉(zhuǎn)換成有意義的結(jié)構(gòu)體,即抽象語法樹(AST【Abstract syntax tree】)。每一個 Go 的源代碼文件最終會被解析成一個獨(dú)立的抽象語法樹歸納成一個source file結(jié)構(gòu)(語法樹最頂層的結(jié)構(gòu)或者開始符號都是 SourceFile)

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

詞法分析會返回一個不包含空格、換行等字符的 Token 序列,例如:package, json, import, (, io, ), …,而語法分析會把 Token 序列轉(zhuǎn)換成有意義的結(jié)構(gòu)體,即語法樹

"json.go": SourceFile {
    PackageName: "json",
    ImportDecl: []Import{
        "io",
    },
    TopLevelDecl: ...
}

2.3 類型檢查

經(jīng)過詞法分析構(gòu)建成抽象語法樹之后,下一步就是類型檢查,類型檢查會對抽象語法樹中定義和使用的類型進(jìn)行檢查,會按照以下步驟處理和驗(yàn)證不同語法樹的節(jié)點(diǎn)。Go語言的編譯器同時使用靜態(tài)類型檢查和動態(tài)類型檢查,這里只討論靜態(tài)類型檢查。

  • 常量、類型和函數(shù)名及類型;
  • 變量的賦值和初始化;
  • 函數(shù)和閉包的主體;
  • 哈希鍵值對的類型;
  • 導(dǎo)入函數(shù)體
  • 外部的聲明

通過對類型的驗(yàn)證,保證節(jié)點(diǎn)不存在類型錯誤,包括:結(jié)構(gòu)體對接口的實(shí)現(xiàn)。類型檢查階段不止會對節(jié)點(diǎn)的類型進(jìn)行驗(yàn)證,還會展開和改寫一些內(nèi)建的函數(shù),例如 make 關(guān)鍵字在這個階段會根據(jù)子樹的結(jié)構(gòu)被替換成 runtime.makeslice 或者 runtime.makechan 等函數(shù)

類型檢查-修改關(guān)鍵字節(jié)點(diǎn)操作類型

注:此過程中也可能改寫AST,包括去除一些不會被執(zhí)行的代碼,優(yōu)化代碼以提高執(zhí)行效率,而且會修改make、new等關(guān)鍵字對應(yīng)節(jié)點(diǎn)的操作類型

2.4 中間代碼生成

經(jīng)過對抽象語法樹的類型檢查后,可以認(rèn)為當(dāng)前代碼不存在類型和語法上的錯誤了,接下來Go編譯器會將抽象語法樹轉(zhuǎn)為中間代碼。中間代碼是編譯器或者虛擬機(jī)使用的語言,它可以來幫助我們分析計(jì)算機(jī)程序。在編譯過程中,編譯器會在將源代碼轉(zhuǎn)換到機(jī)器碼的過程中,先把源代碼轉(zhuǎn)換成一種中間的表示形式。

源代碼-》中間代碼-》機(jī)器碼

中間代碼生成分為三步:配置初始化、遍歷和替換、SSA生成

2.4.1 配置初始化

  • 緩存類型信息
  • 根據(jù)當(dāng)前的 CPU 架構(gòu)初始化 SSA 配置
  • 初始化一些編譯器可能用到的 Go 語言運(yùn)行時的函數(shù)

2.4.2 遍歷和替換

在生成中間代碼之前,編譯器還需要替換抽象語法樹中節(jié)點(diǎn)的一些元素,這個替換的過程是通過cmd/compile/internal/gc.wal和以相關(guān)函數(shù)實(shí)現(xiàn)的。

這些用于遍歷抽象語法樹的函數(shù)會將一些關(guān)鍵字和內(nèi)建函數(shù)轉(zhuǎn)換成函數(shù)調(diào)用,例如: 上述函數(shù)會將panic、recover兩個內(nèi)建函數(shù)轉(zhuǎn)換成runtime.gopanicruntime.gorecover兩個真正運(yùn)行時函數(shù),而關(guān)鍵字new也會被轉(zhuǎn)換成調(diào)用runtime.newobject函數(shù)。

關(guān)鍵字或內(nèi)建函數(shù)到運(yùn)行時函數(shù)的映射

這些映射關(guān)系都在src/cmd/compile/internal/gc/builtin/runtime.go,包括channel、make、new、select等關(guān)鍵字或內(nèi)建函數(shù),但是這里只有聲明。

func makemap64(mapType *byte, hint int64, mapbuf *any) (hmap map[any]any)
func makemap(mapType *byte, hint int, mapbuf *any) (hmap map[any]any)

函數(shù)的實(shí)現(xiàn)是在src/runtime運(yùn)行時包下面,比如對應(yīng)channel的轉(zhuǎn)換后的實(shí)際實(shí)現(xiàn)在src/runtime/chan.go文件

2.4.3 SSA(靜態(tài)單賦值)生成

經(jīng)過 walk 系列函數(shù)的處理之后,抽象語法樹就不會改變了,Go 語言的編譯器會使用 cmd/compile/internal/gc.compileSSA 函數(shù)將抽象語法樹轉(zhuǎn)換成中間代碼

func compileSSA(fn *Node, worker int) {
    f := buildssa(fn, worker)
    pp := newProgs(fn, worker)
    genssa(f, pp)
    pp.Flush()
}

cmd/compile/internal/gc.buildssa負(fù)責(zé)生成具有 SSA 特性的中間代碼,我們可以使用命令行工具來觀察中間代碼的生成過程,假設(shè)我們有以下的 Go 語言源代碼,其中只包含一個簡單的hello函數(shù):

package hello
func hello(a int) int {
    c := a + 2
    return c
}

上述文件中包含源代碼對應(yīng)的抽象語法樹、幾十個版本的中間代碼以及最終生成的 SSA

2.5 機(jī)器碼生成

Go 語言編譯的最后一個階段是根據(jù) SSA 中間代碼生成機(jī)器碼,這里談的機(jī)器碼是在目標(biāo) CPU 架構(gòu)上能夠運(yùn)行的二進(jìn)制代碼。機(jī)器碼的生成實(shí)際上是對SSA的降級過程,在 SSA 中間代碼降級的過程中,編譯器將一些值重寫成了目標(biāo) CPU 架構(gòu)的特定值,降級的過程處理了所有機(jī)器特定的重寫規(guī)則并對代碼進(jìn)行了一定程度的優(yōu)化。執(zhí)行架構(gòu)特定的優(yōu)化和重寫并生成指令,經(jīng)由匯編器將這些指令轉(zhuǎn)換為機(jī)器碼。具體的底層原理就很復(fù)雜了,我也不清楚,了解個過程就行了。

就源碼編譯為匯編指令舉個栗子:

$ cat hello.go
package hello
func hello(a int) int {
    c := a + 2
    return c
}
$ GOOS=linux GOARCH=amd64 go tool compile -S hello.go
"".hello STEXT nosplit size=15 args=0x10 locals=0x0
        0x0000 00000 (hello.go:30)      TEXT    "".hello(SB), NOSPLIT|ABIInternal, $0-16
        0x0000 00000 (hello.go:30)      FUNCDATA        $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x0000 00000 (hello.go:30)      FUNCDATA        $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x0000 00000 (hello.go:31)      MOVQ    "".a+8(SP), AX
        0x0005 00005 (hello.go:31)      ADDQ    $2, AX
        0x0009 00009 (hello.go:32)      MOVQ    AX, "".~r1+16(SP)
        0x000e 00014 (hello.go:32)      RET
        0x0000 48 8b 44 24 08 48 83 c0 02 48 89 44 24 10 c3     H.D$.H...H.D$..
...

3 鏈接過程

編譯過程其實(shí)是對單個文件進(jìn)行的,而鏈接過程將編譯過程生成的一個個目標(biāo)文件鏈接成最終的可執(zhí)行程序,最終得到的文件是分成各種段的,比如數(shù)據(jù)段、代碼段、BSS段等等,運(yùn)行時會被裝載到內(nèi)存中。各個段具有不同的讀寫、執(zhí)行屬性,保護(hù)了程序的安全運(yùn)行。比如Hello.go編譯后會生成一個hello.a二進(jìn)制代碼文件,然后結(jié)合其他庫和基礎(chǔ)庫,在windows下生成一個exe程序。

以上就是深入了解Go語言編譯鏈接的過程的詳細(xì)內(nèi)容,更多關(guān)于Go語言編譯鏈接的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Golang 日期/時間包的使用詳解

    Golang 日期/時間包的使用詳解

    這篇文章主要介紹了Golang 日期/時間包的使用詳解,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-03-03
  • Go 語言數(shù)組和切片的區(qū)別詳解

    Go 語言數(shù)組和切片的區(qū)別詳解

    本文主要介紹了Go 語言數(shù)組和切片的區(qū)別詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-04-04
  • go語言轉(zhuǎn)換json字符串為json數(shù)據(jù)的實(shí)現(xiàn)

    go語言轉(zhuǎn)換json字符串為json數(shù)據(jù)的實(shí)現(xiàn)

    本文主要介紹了go語言轉(zhuǎn)換json字符串為json數(shù)據(jù)的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2025-03-03
  • 十個Go map面試常考問題合集

    十個Go map面試??紗栴}合集

    go面試中,map相關(guān)知識點(diǎn)問的比較多,這篇文章主要為大家整理歸納了10個常考的問題,文中的示例代碼講解詳細(xì),希望對大家有一定的幫助
    2023-07-07
  • Go語言使用讀寫OPC詳解

    Go語言使用讀寫OPC詳解

    這篇文章主要介紹了Go語言使用讀寫OPC詳解,圖文講解的很清晰,有感興趣的同學(xué)可以學(xué)習(xí)下
    2021-03-03
  • Go語言標(biāo)準(zhǔn)輸入輸出庫的基本使用教程

    Go語言標(biāo)準(zhǔn)輸入輸出庫的基本使用教程

    輸入輸出在任何一門語言中都必須提供的一個功能,下面這篇文章主要給大家介紹了關(guān)于Go語言標(biāo)準(zhǔn)輸入輸出庫的基本使用,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-02-02
  • GO語言類型轉(zhuǎn)換和類型斷言實(shí)例分析

    GO語言類型轉(zhuǎn)換和類型斷言實(shí)例分析

    這篇文章主要介紹了GO語言類型轉(zhuǎn)換和類型斷言,以實(shí)例形式詳細(xì)分析了類型轉(zhuǎn)換和類型斷言的概念與使用技巧,需要的朋友可以參考下
    2015-01-01
  • golang中如何使用kafka方法實(shí)例探究

    golang中如何使用kafka方法實(shí)例探究

    Kafka是一種備受歡迎的流處理平臺,具備分布式、可擴(kuò)展、高性能和可靠的特點(diǎn),在處理Kafka數(shù)據(jù)時,有多種最佳實(shí)踐可用來確保高效和可靠的處理,這篇文章將介紹golang中如何使用kafka方法
    2024-01-01
  • go中控制goroutine數(shù)量的方法

    go中控制goroutine數(shù)量的方法

    這篇文章主要介紹了go中控制goroutine數(shù)量的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-05-05
  • Golang實(shí)現(xiàn)帶優(yōu)先級的select

    Golang實(shí)現(xiàn)帶優(yōu)先級的select

    這篇文章主要為大家詳細(xì)介紹了如何在Golang中實(shí)現(xiàn)帶優(yōu)先級的select,文中的示例代碼講解詳細(xì),對我們學(xué)習(xí)Golang有一定的幫助,需要的可以參考一下
    2023-04-04

最新評論