之前也有文章講過go呼叫堆疊的話題,但並沒有完全講清楚,這裡補充裡面缺漏的幾個點。
阻塞
goroutine 1 [select, 2 minutes]:
複製程式碼
方括號裡的select表示阻塞原因,具體定義見runtime.waitReason 。 後面的時間是阻塞時間。需要注意的是這只是一個大概時間,是在gc的過程中標記的,所以如果這個goroutine不需要gc,那麼永遠也不會有值。
PC偏移
github.com/robfig/cron.(*Cron).run(0xc0000c44b0)
cron.go:191 +0x28d
複製程式碼
行號後面的16進位制數是什麼?pc偏移。是指函式入口(cron.(*Cron).run)到呼叫處(也就是行號指位置)的指令位置偏差。一般很少用到,除非下面這種特殊情況:
func main() {
rand.Seed(time.Now().UnixNano())
_, _, _ = foo(), foo(), foo()
}
func foo() int {
if rand.NormFloat64() > 0 {
panic("")
}
return 0
}
//main.main()
// main.go:10 +0x7b 或 +0x80 或 +0x85
複製程式碼
函式引數
函式引數是最複雜的部分,牽涉到go的很多底層實現。
輸入引數,輸出引數
為什麼經常只有一個引數的函式堆疊裡卻跟著兩個數呢?另一個是輸出。
func main() {
rand.Seed(time.Now().UnixNano())
r := rand.Int() //2e78e7b163438cc2
fmt.Printf("%x\n", r)
foo(r)
}
func foo(i int) (o int) {
o = rand.Int() //36dd26e720cac1fe
fmt.Printf("%x\n", o)
defer panic("want to panic")
return
}
//main.foo(2e78e7b163438cc2, 36dd26e720cac1fe)
// main.go:21 +0xdd
複製程式碼
結構體展開
結構體會被展開,然後比較短的欄位會被打包成一個uint。
type S struct {
a int
b int
c int
d int
a1 bool
b1 byte
c1 bool
d1 byte
}
func main() {
foo(S{})
}
func foo(s S) {
panic("want to panic")
noInline()
}
func noInline() {
fmt.Sprint()
}
//main.foo(0x0, 0x0, 0x0, 0x0, 0x0)
// main.go:67 +0x39
複製程式碼
但是這種打包是依賴欄位順序的。
type S struct {
a int
a1 bool
b int
b1 bool
c int
c1 bool
d int
d1 bool
}
func main() {
foo(S{})
}
func foo(s S) {
panic("want to panic")
noInline()
}
func noInline() {
fmt.Sprint()
}
//main.foo(0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0)
// main.go:67 +0x39
複製程式碼
函式引數對照表
下面的表涵蓋了大部分情況
函式簽名 | 輸入 | 堆疊輸出 | 說明 |
---|---|---|---|
foo(float64) | 1 | (0x3ff0000000000000) | |
foo(complex64) | 1+3i | (0x404000003f800000) | 兩個32位浮點打包為一個64位數 |
foo(complex128) | 1+3i | (0x3ff0000000000000, 0x4008000000000000) | |
foo(string) | "中文" | (0x10adc82, 0x6) | (指標,位元組數) |
foo(interface{}) | "" | (0x108e520, 0x10bff30) | (型別指標,值指標) |
foo(interface{}) | (*string)(nil) | (0x108b8e0, 0x0) | 值為nil,型別不為nil |
foo(interface{}) | nil | (0x0, 0x0) | 值,型別都為nil |
foo([]byte) | make([]byte,3,6) | (0xc000070f82, 0x3, 0x6) | (指標,len,cap) |
foo(map[string]string) | make(map[string]string) | (0xc000070e48) | |
foo(chan struct{}) | make(chan struct{}) | (0xc00005e060) | |
foo([200]int) | [200]int{} | (0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, ...) | 陣列展開 |
foo(func()) | nil | (0x0) | |
foo(int16, uint16) | 1,2 | (0xc000020001) | 兩個16位數打包為32位數,僅低32位有效 |
foo(bool)bool | false | (0xc000070f00, 0xc00005c058) | 前一個數為輸入,僅低8位有效,後一個數為輸出,未初始化 |
foo(struct {a,b int}) | struct {a,b int}{} | (0x0, 0x0) | 結構體展開 |
foo(struct {a int;b bool;c int;d bool;e byte}) | struct {a int;b bool;c byte;d bool;e byte}{1,true,2,false,3} | (0x1, 0x1, 0x2, 0x300) | d,e合併,b不合並 |
foo(struct {a int;b bool;c string;d bool;e byte}) | struct {a int;b bool;c string;d bool;e byte}{1,true,"a",false,3} | (0x1, 0x1, 0x10adb18, 0x1, 0x300) | d,e合併 |
foo(struct {a struct{a,b byte};b bool}) | struct {a struct{a,b byte};b bool}{struct{a,b byte}{3,4},true} | (0xc000010403) | 內嵌結構體合併 |
foo(struct {a struct{a int;b byte};b bool}) | struct {a struct{a int;b byte};b bool}{struct {a int;b byte}{5 , 7 },false} | (0x5, 0xc000072f07, 0xc00005e000) | 內嵌結構體不合並 |