## 節點樹
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)`所示。
- 介紹
- 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 本章小結