使用errors包时runtime.Callers返回PC对应函数名不符问题
Go代码返回值差异导致调用栈信息不同的原因分析
两段Go代码仅在Test1()的返回值上存在差异:code1返回errors.New("Test"),code2返回nil。运行后code1能正确打印出main.Test1函数名,而code2无法输出该函数名,仅显示main.main;但在Goland调试模式下,两段代码均能正常输出main.Test1。
代码示例
code1
package main import ( "fmt" "github.com/pkg/errors" "runtime" ) func main() { ptrs, _ := Test1("arg1", "arg2") for _, pc := range *ptrs { f := runtime.FuncForPC(pc) if f == nil { continue } filename, line := f.FileLine(pc) fmt.Printf("func name: %s, entry: %x, filename: %s, line: %d\n", f.Name(), f.Entry(), filename, line) } } func Test0(args ...interface{}) *[]uintptr { const depth = 32 var pcs [depth]uintptr n := runtime.Callers(0, pcs[:]) st := pcs[0:n] return &st } func Test1(args ...interface{}) (*[]uintptr, error) { return Test0(args...), errors.New("Test") }
code2
package main import ( "fmt" "runtime" ) func main() { ptrs, _ := Test1("arg1", "arg2") for _, pc := range *ptrs { f := runtime.FuncForPC(pc) if f == nil { continue } filename, line := f.FileLine(pc) fmt.Printf("func name: %s, entry: %x, filename: %s, line: %d\n", f.Name(), f.Entry(), filename, line) } } func Test0(args ...interface{}) *[]uintptr { const depth = 32 var pcs [depth]uintptr n := runtime.Callers(0, pcs[:]) st := pcs[0:n] return &st } func Test1(args ...interface{}) (*[]uintptr, error) { return Test0(args...), nil//, errors.New("Test") }
运行结果
code1运行结果
func name: runtime.Callers, entry: 6a20c0, filename: C:/Program Files/Go/src/runtime/extern.go, line: 247 func name: runtime.Callers, entry: 6a20c0, filename: C:/Program Files/Go/src/runtime/extern.go, line: 247 func name: main.Test1, entry: 6a2180, filename: D:/develop/go-testbed/cmd/slicetest/slicetest.go, line: 30 func name: main.main, entry: 6a1ea0, filename: D:/develop/go-testbed/cmd/slicetest/slicetest.go, line: 11 func name: runtime.main, entry: 6476a0, filename: C:/Program Files/Go/src/runtime/proc.go, line: 259 func name: runtime.goexit, entry: 66eee0, filename: C:/Program Files/Go/src/runtime/asm_amd64.s, line: 1595
code2运行结果
func name: runtime.Callers, entry: b30020, filename: C:/Program Files/Go/src/runtime/extern.go, line: 247 func name: runtime.Callers, entry: b30020, filename: C:/Program Files/Go/src/runtime/extern.go, line: 247 func name: main.main, entry: b2fe00, filename: D:/develop/go-testbed/cmd/slicetest/slicetest.go, line: 10 func name: main.main, entry: b2fe00, filename: D:/develop/go-testbed/cmd/slicetest/slicetest.go, line: 9 func name: runtime.main, entry: ad6fc0, filename: C:/Program Files/Go/src/runtime/proc.go, line: 259 func name: runtime.goexit, entry: afe580, filename: C:/Program Files/Go/src/runtime/asm_amd64.s, line: 1595
差异原因分析
核心原因是Go编译器的内联优化:
- 当
Test1返回nil时,函数逻辑极度简单——仅调用Test0并返回其结果和nil,Go编译器会自动将Test1的代码内联到调用它的main函数中。此时runtime.Callers收集的调用栈里不会有Test1的帧,因为它的代码已经被合并到main函数里,相当于main直接调用了Test0。 - 当
Test1返回errors.New("Test")时,errors.New内部涉及内存分配等相对复杂的操作,编译器判定该函数不适合内联,因此调用栈中会保留Test1的帧,能被runtime.Callers捕获到。
调试模式下(编译器默认关闭内联优化,对应参数-gcflags="-N -l"),两段代码都不会触发内联,因此都能在调用栈中看到main.Test1,这也验证了上述结论。
内容的提问来源于stack exchange,提问作者Hodol
相关产品推荐
相关产品推荐

