[go]go如何把代码运行起来的?

参考

代码在硬盘上是一堆二进制

  • 弄清楚文件在硬盘/内存中的存储值
package main

import "fmt"

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

vim查看 :%!xxd

在终端里执行 man ascii

观察发现, 中间列和最右列 是一一对应的。
也就是说,刚刚写完的 hello.go 文件都是由 ASCII 字符表示的(文本文件)

  • 汇编转换位机器指令

  • go语句转换为机器指令过程

Go 程序并不能直接运行,每条 Go 语句必须转化为一系列的低级机器语言指令,将这些指令打包到一起,
并以二进制磁盘文件的形式存储起来,也就是可执行目标文件。

探索编译和运行的过程

通常将编译和链接合并到一起的过程称为构建(Build)。

编译过程就是对源文件进行词法分析、语法分析、语义分析、优化,最后生成汇编代码文件,以 .s 作为文件后缀的汇编指令。
汇编器会将汇编代码转变成机器可以执行的指令。
由于每一条汇编语句几乎都与一条机器指令相对应,所以只是一个简单的一一对应,比较简单,没有语法、语义分析,也没有优化这些步骤。
  • 编译器的作用: 将高级语言翻译成机器语言

6个阶段

  • 词法分析

使用一种有限状态机的算法

有限自动机是有限个状态的自动机器。
我们可以拿抽水马桶举例,它分为两个状态:“注水”和“水满”。
摁下冲马桶的按钮,它转到“注水”的状态,而浮球上升到一定高度,就会把注水阀门关闭,它转到“水满”状态。

我们会识别出if、else、int这样的关键字,main、printf、age这样的标识符,+、-、=这样的操作符号,还有花括号、圆括号、分号这样的符号,以及数字字面量、字符串字面量等。这些都是Token。

token一般分为这几类:关键字、标识符、字面量(包含数字、字符串)、特殊符号(如加号、等号)。

slice[i] = i * (2 + 6)

总共包含 16 个非空字符,经过扫描(对那棵树 递归下降算法(Recursive Descent Parsing)构建一棵完整的树)后,

- src/cmd/compile/internal/syntax/scanner.go
最关键的函数就是 next 函数,它不断地读取下一个字符(go utf8以字符为单位)
func (s *scanner) next() {


- src/cmd/compile/internal/syntax/token.go

var tokstrings = [...]string{
    // source control
    _EOF: "EOF",

    // names and literals
    _Name:    "name",
    _Literal: "literal",

    // operators and operations
    _Operator: "op",
    _AssignOp: "op=",
    _IncOp:    "opop",
    _Assign:   "=",
    _Define:   ":=",
    _Arrow:    "<-",
    _Star:     "*",

    // delimitors
    _Lparen:    "(",
    _Lbrack:    "[",
    _Lbrace:    "{",
    _Rparen:    ")",
    _Rbrack:    "]",
    _Rbrace:    "}",
    _Comma:     ",",
    _Semi:      ";",
    _Colon:     ":",
    _Dot:       ".",
    _DotDotDot: "...",

    // keywords
    _Break:       "break",
    _Case:        "case",
    _Chan:        "chan",
    _Const:       "const",
    _Continue:    "continue",
    _Default:     "default",
    _Defer:       "defer",
    _Else:        "else",
    _Fallthrough: "fallthrough",
    _For:         "for",
    _Func:        "func",
    _Go:          "go",
    _Goto:        "goto",
    _If:          "if",
    _Import:      "import",
    _Interface:   "interface",
    _Map:         "map",
    _Package:     "package",
    _Range:       "range",
    _Return:      "return",
    _Select:      "select",
    _Struct:      "struct",
    _Switch:      "switch",
    _Type:        "type",
    _Var:         "var",
}

  • 语法分析
    例如“2+3*5”,你会得到一棵类似下图的AST。

  • 语义分析

以“You can never drink too much water.” 这句话为例。它的确切含义是什么?
是“你不能喝太多水”,
还是“你喝多少水都不嫌多”?

实际上,这两种解释都是可以的,我们只有联系上下文才能知道它的准确含义。

词法分析是把程序分割成一个个Token的过程,可以通过构造有限自动机来实现。

语法分析是把程序的结构识别出来,并形成一棵便于由计算机处理的抽象语法树。可以用递归下降的算法来实现。

语义分析是消除语义模糊,生成一些属性信息,让计算机能够依据这些信息生成目标代码。
  • 目标代码生成与优化
    不同机器的机器字长、寄存器等等都不一样,意味着在不同机器上跑的机器码是不一样的。最后一步的目的就是要生成能在不同 CPU 架构上运行的代码。
    为了榨干机器的每一滴油水,目标代码优化器会对一些指令进行优化,例如使用移位指令代替乘法指令等。

  • 链接
    链接过程就是要把编译器生成的一个个目标文件链接成可执行文件。最终得到的文件是分成各种段的,比如数据段、代码段、BSS段等等,运行时会被装载到内存中。各个段具有不同的读写、执行属性,保护了程序的安全运行。

从上图: 将编写的一个c程序(源代码 )转换成可以在硬件上运行的程序(可执行代码 ),需要进行
    编译阶段
        先通过“编译器 “把一个 .c/.cpp 源代码 编译成 .s的汇编代码;
        再经过“汇编器 ” 把这 个.s的汇编代码汇编成 .o 的 目标代码
    链接阶段
        通过连接其他 .o 代码(如果需要的话) 库文件 和 1 中的.o 目标代码生成可执行文件

该文件流被这三种程序(红色)的加工,分别表现出四种形式(蓝色),这就是c程序的编译和链接过程。

如果再详细的话,编译器在将源文件编译成汇编文件的过程又分为:预处理阶段(生成 .i 代码) 和  优化阶段

go编译流程查看

posted @ 2020-01-11 14:41  mmaotai  阅读(1278)  评论(0编辑  收藏  举报