You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 23:48:22