之前也有文章講過(guò)go調(diào)用堆棧的話題惊奇,但并沒(méi)有完全講清楚朦佩,這里補(bǔ)充里面缺漏的幾個(gè)點(diǎn)捺萌。
阻塞
goroutine 1 [select, 2 minutes]:
方括號(hào)里的select表示阻塞原因,具體定義見runtime.waitReason 新博。
后面的時(shí)間是阻塞時(shí)間薪夕。需要注意的是這只是一個(gè)大概時(shí)間,是在gc的過(guò)程中標(biāo)記的赫悄,所以如果這個(gè)goroutine不需要gc原献,那么永遠(yuǎn)也不會(huì)有值。
PC偏移
github.com/robfig/cron.(*Cron).run(0xc0000c44b0)
cron.go:191 +0x28d
行號(hào)后面的16進(jìn)制數(shù)是什么埂淮?pc偏移姑隅。是指函數(shù)入口(cron.(*Cron).run)到調(diào)用處(也就是行號(hào)指位置)的指令位置偏差。一般很少用到倔撞,除非下面這種特殊情況:
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
函數(shù)參數(shù)
函數(shù)參數(shù)是最復(fù)雜的部分讲仰,牽涉到go的很多底層實(shí)現(xiàn)。
輸入?yún)?shù)误窖,輸出參數(shù)
為什么經(jīng)常只有一個(gè)參數(shù)的函數(shù)堆棧里卻跟著兩個(gè)數(shù)呢叮盘?另一個(gè)是輸出。
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
結(jié)構(gòu)體展開
結(jié)構(gòu)體會(huì)被展開霹俺,然后比較短的字段會(huì)被打包成一個(gè)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
函數(shù)參數(shù)對(duì)照表
下面的表涵蓋了大部分情況
函數(shù)簽名 | 輸入 | 堆棧輸出 | 說(shuō)明 |
---|---|---|---|
foo(float64) | 1 | (0x3ff0000000000000) | |
foo(complex64) | 1+3i | (0x404000003f800000) | 兩個(gè)32位浮點(diǎn)打包為一個(gè)64位數(shù) |
foo(complex128) | 1+3i | (0x3ff0000000000000, 0x4008000000000000) | |
foo(string) | "中文" | (0x10adc82, 0x6) | (指針丙唧,字節(jié)數(shù)) |
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, ...) | 數(shù)組展開 |
foo(func()) | nil | (0x0) | |
foo(int16, uint16) | 1,2 | (0xc000020001) | 兩個(gè)16位數(shù)打包為32位數(shù)胡本,僅低32位有效 |
foo(bool)bool | false | (0xc000070f00, 0xc00005c058) | 前一個(gè)數(shù)為輸入牌柄,僅低8位有效,后一個(gè)數(shù)為輸出侧甫,未初始化 |
foo(struct {a,b int}) | struct {a,b int}{} | (0x0, 0x0) | 結(jié)構(gòu)體展開 |
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) | 內(nèi)嵌結(jié)構(gòu)體合并 |
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) | 內(nèi)嵌結(jié)構(gòu)體不合并 |