《Go语言设计与实现》读书笔记-第一部分

1.1 调试源代码

1.1.1 编译源码

  1. 下载Go1.4并安装到~/go1.4路径下(Go从1.5之版本开始实现自举,编译需要用到1.4版本。~/go1.4是编译时默认的读取路径,可以通过设置环境变量$GOROOT_BOOTSTRAP来指定):

    官网:Downloads - The Go Programming Language

    国内镜像:Go下载 - Go语言中文网 - Golang中文社区

  2. 修改源码,比如在fmt/print.goPrintln函数打印时多加一句:

    /usr/local/go/src/fmt/print.go:

    ...
    273 func Println(a ...interface{}) (n int, err error) {
    274         println("hisoka")
    275         return Fprintln(os.Stdout, a...)
    276 }
    ...
  3. 切换到go的安装路径里的src目录(不可省略,否则报错),运行make.bash脚本:

    % cd /usr/local/Cellar/go/1.17.5/libexec/src
    % sudo ./make.bash
    Building Go cmd/dist using /Users/hisoka/go1.4. (go1.14 darwin/amd64)
    Building Go toolchain1 using /Users/hisoka/go1.4.
    Building Go bootstrap cmd/go (go_bootstrap) using Go toolchain1.
    Building Go toolchain2 using go_bootstrap and Go toolchain1.
    Building Go toolchain3 using go_bootstrap and Go toolchain2.
    Building packages and commands for darwin/amd64.
    ---
    Installed Go for darwin/amd64 in /usr/local/Cellar/go/1.17.5/libexec
    Installed commands in /usr/local/Cellar/go/1.17.5/libexec/bin
  4. 写个Hello World程序验证下效果:

    % go run hello.go
    hisoka
    Hello World!

1.1.2 中间代码

  • 获取汇编代码:

    % go build -gcflags -S hello.go
    # command-line-arguments
    "".main STEXT size=103 args=0x0 locals=0x40 funcid=0x0
        0x0000 00000 (/Users/hisoka/Downloads/hello.go:5)    TEXT    "".main(SB), ABIInternal, $64-0
    ...
  • 获取编译指令优化过程:

    % GOSSAFUNC=main go build hello.go
    # runtime
    dumped SSA to /Users/hisoka/Downloads/ssa.html
    # command-line-arguments
    dumped SSA to ./ssa.html

2.1 编译过程

2.1.1 预备知识

  • 抽象语法树(Abstract Syntax Tree、AST),是源代码语法的结构的一种抽象表示,它用树状的方式表示编程语言的语法结构。
  • 静态单赋值(Static Single Assignment、SSA)是中间代码的特性,如果中间代码具有静态单赋值的特性,那么每个变量就只会被赋值一次。
  • 指令集

2.1.2 编译原理

《Go语言设计与实现》读书笔记-第一部分_第1张图片

  • 词法与语法分析:

    所有的编译过程其实都是从解析代码的源文件开始的,词法分析的作用就是解析源代码文件,它将文件中的字符串序列转换成 Token 序列,方便后面的处理和解析,我们一般会把执行词法分析的程序称为词法解析器(lexer)。

    而语法分析的输入是词法分析器输出的 Token 序列,语法分析器会按照顺序解析 Token 序列,该过程会将词法分析生成的 Token 按照编程语言定义好的文法(Grammar)自下而上或者自上而下的规约,每一个 Go 的源代码文件最终会被归纳成一个 SourceFile 结构。

  • 类型检查:

    当拿到一组文件的抽象语法树之后,Go 语言的编译器会对语法树中定义和使用的类型进行检查,类型检查会按照以下的顺序分别验证和处理不同类型的节点:

    1. 常量、类型和函数名及类型;
    2. 变量的赋值和初始化;
    3. 函数和闭包的主体;
    4. 哈希键值对的类型;
    5. 导入函数体;
    6. 外部的声明;
  • 中间代码生成:

    当我们将源文件转换成了抽象语法树、对整棵树的语法进行解析并进行类型检查之后,就可以认为当前文件中的代码不存在语法错误和类型错误的问题了,Go 语言的编译器就会将输入的抽象语法树转换成中间代码。

  • 机器码生成:

    Go 语言源代码的 src/cmd/compile/internal目录中包含了很多机器码生成相关的包,不同类型的 CPU 分别使用了不同的包生成机器码,其中包括 amd64、arm、arm64、mips、mips64、ppc64、s390x、x86 和 wasm,其中比较有趣的就是 WebAssembly(Wasm)了。

2.1.3 编译器入口

Go 语言的编译器入口在src/cmd/compile/internal/gc/main.go文件中,其中 600 多行的 cmd/compile/internal/gc.Main就是 Go 语言编译器的主程序。

2.2 词法与语法分析

2.3 类型检查

2.3.1 强弱类型

  • 一般结论:

    • 强类型的编程语言在编译期间会有更严格的类型限制,也就是编译器会在编译期间发现变量赋值、返回值和函数调用时的类型错误;
    • 弱类型的编程语言在出现类型错误时可能会在运行时进行隐式的类型转换,在类型转换时可能会造成运行错误。
  • 开发者更值得关注的问题:

    • 类型的转换是显式的还是隐式的?
    • 编译器会帮助我们推断变量的类型么?

2.3.2 静态类型与动态类型

  • 静态类型检查:

    静态类型检查是基于对源代码的分析来确定运行程序类型安全的过程,如果我们的代码能够通过静态类型检查,那么当前程序在一定程度上可以满足类型安全的要求,它能够减少程序在运行时的类型检查,也可以被看作是一种代码优化的方式。

  • 动态类型检查:

    动态类型检查是在运行时确定程序类型安全的过程,它需要编程语言在编译时为所有的对象加入类型标签等信息,运行时可以使用这些存储的类型信息来实现动态派发、向下转型、反射以及其他特性。动态类型检查能为工程师提供更多的操作空间,让我们能在运行时获取一些类型相关的上下文并根据对象的类型完成一些动态操作。

2.4 中间代码生成 ~ 2.5 机器码生成

你可能感兴趣的