ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
## 节点树 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`参数告诉 g​​o 编译命令在类型检查后打印**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)`所示。