之前也有文章讲过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) | 内嵌结构体不合并 |
有疑问加站长微信联系(非本文作者)