## 节点树
Go 节点是包含大量属性的结构。你将在第 4 章“复合类型的使用”中学习有关定义和使用 Go struct的更多信息。Go 编译器的模块根据 Go 编程语言的语法对 Go 程序中的所有内容进行解析和分析。此分析的最终结果是一棵树,该树对应于所提供的 Go 代码,并以适合编译器方式表示程序结构。
> Tip: 需要注意的是,`go tool 6g -W test.go`在更新的 Go 版本中无法正常运行。取而代之你应该使用`go tool compile -W test.go`。
本节将首先使用以下 Go 代码(保存为`nodeTree.go`)作为示例,以查看 go 工具可以为我们提供的底层信息:
```Go
package main
import (
"fmt"
)
func main() {
fmt.Println("Hello there!")
}
```
nodeTree.go 的 Go 代码非常容易理解,因此你不会对它感到惊讶其输出,接下来是:
```shell
$ go run nodeTree.go
Hello there!
```
通过执行下一个命令来查看一些 Go 的内部工作方式:
```shell
$ go tool compile -W nodeTree.go
before walk main
. CALLFUNC l(8) tc(1) STRUCT-(int, error)
. . NAME-fmt.Println a(true) l(263) x(0) class(PFUNC) tc(1) used FUNC-
func(...interface {}) (int, error)
. . DDDARG l(8) esc(no) PTR64-*[1]interface {}
. CALLFUNC-list
. . CONVIFACE l(8) esc(h) tc(1) implicit(true) INTER-interface {}
. . . NAME-main.statictmp_0 a(true) l(8) x(0) class(PEXTERN) tc(1)
used string
. VARKILL l(8) tc(1)
. . NAME-main..autotmp_0 a(true) l(8) x(0) class(PAUTO) esc(N) used
ARRAY-[1]interface {}
after walk main
. CALLFUNC-init
. . AS l(8) tc(1)
. . . NAME-main..autotmp_0 a(true) l(8) x(0) class(PAUTO) esc(N)
tc(1) addrtaken assigned used ARRAY-[1]interface {}
.. AS l(8) tc(1)
.. . NAME-main..autotmp_2 a(true) l(8) x(0) class(PAUTO) esc(N) tc(1) assigned used PTR64-*[1]interface {}
.. . ADDR l(8) tc(1) PTR64-*[1]interface {}
.. . . NAME-main..autotmp_0 a(true) l(8) x(0) class(PAUTO) esc(N) tc(1) addrtaken assigned used ARRAY-[1]interface {}
. . BLOCK l(8)
. . BLOCK-list
. . . AS l(8) tc(1) hascall
. . . . INDEX l(8) tc(1) assigned bounded hascall INTER-interface
{}
. . . . . IND l(8) tc(1) implicit(true) assigned hascall ARRAY-
[1]interface {}
. . . . . . NAME-main..autotmp_2 a(true) l(8) x(0) class(PAUTO)
esc(N) tc(1) assigned used PTR64-*[1]interface {}
. . . . . LITERAL-0 l(8) tc(1) int
. .
. .
. . uint8
.. . . . ADDR l(8) tc(1) PTR64-*string
.. . . . . NAME-main.statictmp_0 a(true) l(8) x(0) class(PEXTERN) tc(1) addrtaken used string
. .
. . . ADDR a(true) l(8) tc(1) PTR64-*uint8
. . . . NAME-type.string a(true) x(0) class(PEXTERN) tc(1)
EFACE l(8) tc(1) INTER-interface {}
. . BLOCK l(8)
. . BLOCK-list
. . . AS l(8) tc(1) hascall
. . . . NAME-main..autotmp_1 a(true) l(8) x(0) class(PAUTO) esc(N)
tc(1) assigned used SLICE-[]interface {}
.. . . SLICEARR l(8) tc(1) hascall SLICE-[]interface {}
.. . . . NAME-main..autotmp_2 a(true) l(8) x(0) class(PAUTO) esc(N) tc(1) assigned used PTR64-*[1]interface {}
. CALLFUNC l(8) tc(1) hascall STRUCT-(int, error)
. . NAME-fmt.Println a(true) l(263) x(0) class(PFUNC) tc(1) used FUNC-
func(...interface {}) (int, error)
. . DDDARG l(8) esc(no) PTR64-*[1]interface {}
. CALLFUNC-list
. . AS l(8) tc(1)
. . . INDREGSP-SP a(true) l(8) x(0) tc(1) addrtaken main.__ SLICE-
[]interface {}
. . . NAME-main..autotmp_1 a(true) l(8) x(0) class(PAUTO) esc(N)
tc(1) assigned used SLICE-[]interface {}
. VARKILL l(8) tc(1)
. . NAME-main..autotmp_0 a(true) l(8) x(0) class(PAUTO) esc(N) tc(1)
addrtaken assigned used ARRAY-[1]interface {}
before walk init
. IF l(1) tc(1)
. . GT l(1) tc(1) bool
. . . NAME-main.initdone a(true) l(1) x(0) class(PEXTERN) tc(1)
assigned used uint8
. . . LITERAL-1 l(1) tc(1) uint8
. IF-body
. . RETURN l(1) tc(1)
. IF l(1) tc(1)
. . EQ l(1) tc(1) bool
. . . NAME-main.initdone a(true) l(1) x(0) class(PEXTERN) tc(1)
assigned used uint8
. . . LITERAL-1 l(1) tc(1) uint8
. IF-body
. . CALLFUNC l(1) tc(1)
. . . NAME-runtime.throwinit a(true) x(0) class(PFUNC) tc(1) used
FUNC-func()
. AS l(1) tc(1)
. . NAME-main.initdone a(true) l(1) x(0) class(PEXTERN) tc(1) assigned
used uint8
. . LITERAL-1 l(1) tc(1) uint8
. CALLFUNC l(1) tc(1)
. . NAME-fmt.init a(true) l(1) x(0) class(PFUNC) tc(1) used FUNC-func()
. AS l(1) tc(1)
. . NAME-main.initdone a(true) l(1) x(0) class(PEXTERN) tc(1) assigned
used uint8
. . LITERAL-2 l(1) tc(1) uint8
. RETURN l(1) tc(1)
after walk init
. IF l(1) tc(1)
. . GT l(1) tc(1) bool
. . . NAME-main.initdone a(true) l(1) x(0) class(PEXTERN) tc(1)
assigned used uint8
. . . LITERAL-1 l(1) tc(1) uint8
. IF-body
. . RETURN l(1) tc(1)
. IF l(1) tc(1)
. . EQ l(1) tc(1) bool
. . . NAME-main.initdone a(true) l(1) x(0) class(PEXTERN) tc(1)
assigned used uint8
. . . LITERAL-1 l(1) tc(1) uint8
. IF-body
. . CALLFUNC l(1) tc(1) hascall
. . . NAME-runtime.throwinit a(true) x(0) class(PFUNC) tc(1) used
FUNC-func()
. AS l(1) tc(1)
. . NAME-main.initdone a(true) l(1) x(0) class(PEXTERN) tc(1) assigned
used uint8
. . LITERAL-1 l(1) tc(1) uint8
. CALLFUNC l(1) tc(1) hascall
. . NAME-fmt.init a(true) l(1) x(0) class(PFUNC) tc(1) used FUNC-func()
. AS l(1) tc(1)
. . NAME-main.initdone a(true) l(1) x(0) class(PEXTERN) tc(1) assigned
used uint8
. . LITERAL-2 l(1) tc(1) uint8
. RETURN l(1) tc(1)
```
Go 编译器及其工具在后台做很多事情,即使对于像`nodeTree.go`这样的简单程序也是如此。
> Tip: `-W`参数告诉 go 编译命令在类型检查后打印**debug parse tree**。
查看下面两个命令的输出:
```shell
$ go tool compile -W nodeTree.go | grep before
before walk main
before walk init
$ go tool compile -W nodeTree.go | grep after
after walk main
after walk init
```
由以上输出可以知道,`before`关键字是关于函数执行开始的。如果你的程序具有更多功能,你将获得更多输出,比如:
```shell
$ go tool compile -W defer.go | grep before
before d1
before d2
before d3
before main
before d2.func1
before d3.func1
before init
before type..hash.[2]interface {}
before type..eq.[2]interface {}
```
前面的示例使用`defer.go`的 Go 代码,该代码比`nodeTree.go`复杂得多。但是,很显然,`init()`函数是 Go 自动生成的,因为它也在`go tool compile -W`的两个输出中(`nodeTree.go`和`defer.go`)。现在,我将向你展示一个名为`nodeTreeMore.go`输出,它是一个多版本的`nodeTree.go`:
```Go
package main
import (
"fmt"
)
func functionOne(x int) {
fmt.Println(x)
}
func main() {
varOne := 1
varTwo := 2
fmt.Println("Hello there!")
functionOne(varOne)
functionOne(varTwo)
}
```
`nodeTreeMore.go`程序有两个变量,分别名为`varOne`和`varTwo`,以及一个名为`functionOne`的附加函数。在`go tool compile -W`输出中搜索`varOne,varTwo`和`functionOne`将显示以下信息:
```shell
$ go tool compile -W nodeTreeMore.go | grep functionOne | uniq
before walk functionOne
after walk functionOne
. . NAME-main.functionOne a(true) l(7) x(0) class(PFUNC) tc(1) used
FUNC-func(int)
$ go tool compile -W nodeTreeMore.go | grep varTwo | uniq
. . NAME-main.varTwo a(true) g(2) l(13) x(0) class(PAUTO) tc(1) used
int
. . . NAME-main.varTwo a(true) g(2) l(13) x(0) class(PAUTO) tc(1)
used int
$ go tool compile -W nodeTreeMore.go | grep varOne | uniq
. . NAME-main.varOne a(true) g(1) l(12) x(0) class(PAUTO) tc(1) used
int
. . . NAME-main.varOne a(true) g(1) l(12) x(0) class(PAUTO) tc(1)
used int
```
因此,`varOne`表示为`NAME-main.varOne`,而`varTwo`表示为`NAME-main.varTwo`。 `functionOne()`函数被称为`NAME-main.functionOne`,`main()`函数被表示为`NAME-main`。
现在,让我们看看`nodeTreeMore.go`的 debug parse tree:
```shell
before walk functionOne
. AS l(8) tc(1)
. . NAME-main..autotmp_2 a(true) l(8) x(0) class(PAUTO) esc(N) tc(1)
assigned used int
. . NAME-main.x a(true) g(1) l(7) x(0) class(PPARAM) tc(1) used int
```
这段信息与`functionOne()`的定义有关。 `l(8)`字符串告诉我们,可以在第八行(即在读取第七行之后)找到该节点的定义。 `NAME-main..autotmp_2`整数变量由编译器自动生成。
debug parse tree 输出的下一部分将在此处进行说明:
```shell
. CALLFUNC l(15) tc(1)
. . NAME-main.functionOne a(true) l(7) x(0) class(PFUNC) tc(1) used
FUNC-func(int)
. CALLFUNC-list
. . NAME-main.varOne a(true) g(1) l(12) x(0) class(PAUTO) tc(1) used
int
```
第一行说在程序的第15行(由`l(15)`指定),你将调用`NAME-main.functionOne`,该名称在程序的第7行定义,由`l(7)`指定,即一个需要一个整数参数的函数,该函数由`FUNC func(int)`指定。在`CALLFUNC-list`之后指定的参数函数列表包括`NAME-main.varOne`变量,该变量在程序的第12行定义,如`l(12)`所示。
- 介绍
- 1 Go与操作系统
- 01.1 Go的历史
- 01.2 Go的未来
- 01.3 Go的优点
- 01.3.1 Go是完美的么
- 01.3.2 什么是预处理器
- 01.3.3 godoc
- 01.4 编译Go代码
- 2 理解 Go 的内部构造
- Go 编译器
- Go 的垃圾回收
- 三色算法
- 有关 Go 垃圾收集器操作的更多信息
- Maps, silces 与 Go 垃圾回收器
- Unsafe code
- 有关 unsafe 包
- 另一个 usafe 包的例子
- 从 Go 调用 C 代码
- 在同一文件用 Go 调用 C 代码
- 在单独的文件用 Go 调用 C 代码
- 从 C 调用 Go 代码
- Go 包
- C 代码
- defer 关键字
- 用 defer 打印日志
- Panic 和 Recover
- 单独使用 Panic 函数
- 两个好用的 UNIX 工具
- strace
- dtrace
- 配置 Go 开发环境
- go env 命令
- Go 汇编器
- 节点树
- 进一步了解 Go 构建
- 创建 WebAssembly 代码
- 对 Webassembly 的简单介绍
- 为什么 WebAssembly 很重要
- Go 与 WebAssembly
- 示例
- 使用创建好的 WebAssembly 代码
- Go 编码风格建议
- 练习和相关链接
- 本章小结
- 3 Go基本数据类型
- 03.1 Go循环
- 03.1.1 for循环
- 03.1.2 while循环
- 03.1.3 range关键字
- 03.1.4 for循环代码示例
- 03.3 Go切片
- 03.3.1 切片基本操作
- 03.3.2 切片的扩容
- 03.3.3 字节切片
- 03.3.4 copy()函数
- 03.3.5 多维切片
- 03.3.6 使用切片的代码示例
- 03.3.7 使用sort.Slice()排序
- 03.4 Go 映射(map)
- 03.4.1 Map值为nil的坑
- 03.4.2 何时该使用Map?
- 03.5 Go 常量
- 03.5.1 常量生成器:iota
- 03.6 Go 指针
- 03.7 时间与日期的处理技巧
- 03.7.1 解析时间
- 03.7.2 解析时间的代码示例
- 03.7.3 解析日期
- 03.7.4 解析日期的代码示例
- 03.7.5 格式化时间与日期
- 03.8 延伸阅读
- 03.9 练习
- 03.10 本章小结
- 9 并发-Goroutines,Channel和Pipeline
- 09.1 关于进程,线程和Go协程
- 09.1.1 Go调度器
- 09.1.2 并发与并行
- 09.2 Goroutines
- 09.2.1 创建一个Goroutine
- 09.2.2 创建多个Goroutine
- 09.3 优雅地结束goroutines
- 09.3.1 当Add()和Done()的数量不匹配时会发生什么?
- 09.4 Channel(通道)
- 09.4.1 通道的写入
- 09.4.2 从通道接收数据
- 09.4.3 通道作为函数参数传递
- 09.5 管道
- 09.6 延展阅读
- 09.7 练习
- 09.8 本章小结