Go Caller的使用

跟着书做项目碰到了不认识的函数

在边看书边敲代码的时候,碰到了一个不认识的函数,在学习 Golang 的基础的时候也没碰到过
但是网上一搜,感觉又特别重要,所以琢磨了一个多小时,总算是明白是拿来做什么的了
这个函数就是 runtime.Caller 及其他一起使用的函数

获取函数信息

在很多文章里, Caller 出现最多的就是在日志部分
因为它可以拿来获取调用者的各种信息,比如名称,行号等
比如如下函数

1
2
3
4
5
6
7
8
9
10
11
func printName() string {
pc, _, _, _ := runtime.Caller(1) // 本函数向上推一层即可获取调用者的 program counter(程序计数器)
return runtime.FuncForPC(pc).Name()
}

func printCallerName() string {
{
pc, _, _, _ := runtime.Caller(2)
return runtime.FuncForPC(pc).Name()
}
}

printName() 获取本函数名称
printCallerName() 获取调用函数名称
runtime.Caller 需要传入一个 skip 参数,skip 是堆栈向前推几个的数量
因为函数的调用是压栈操作,0就表示本函数,1就表示向前推一个函数,printName()本身就是一个函数,所以往前推一个就是调用者了
printCallerName()是用来获取调用者的函数,0表示其本身,1表示调用者,2就表示调用者的调用者,也就是我们想获取的调用者

1
2
3
4
5
6
7
8
9
10
11
12
func main() {
hello()
}

func hello() {
fmt.Printf("我是 %s, %s 在调用我\n", printName(), printCallerName())
world()
}

func world() {
fmt.Printf("我是 %s, %s 在调用我\n", printName(), printCallerName())
}

输出如下

1
2
我是 main.hello, main.main 在调用我
我是 main.world, main.hello 在调用我

目前碰到的几个函数的使用

func Caller(skip int) (pc uintptr, file string, line int, ok bool)

就是前面使用的函数,需要传入一个 skip 参数表示以本函数为0向前推几个函数
返回4个值,分别为

  • pc 指向程序计数器的 uintptr 指针, uintptr 指针不是真正的指针,它不能指向对象,但它可以进行指针的加减,就跟 C 里面的指针一样,使用时需要把它转换为 Pointer,通过*操作来取值,赋值
  • file 字符串型,名称
  • line int型,行号
  • ok 布尔值,用来表示是否成功获取

func Callers(skip int, pc []uintptr) int

将连续的程序计数器放到 pc 指针中,直到指针的空间放不下了为止,skip表示从谁开始,不过0表示 Callers() 函数本身,1才表示本函数,这里和Caller() 是有一点差别的

1
2
3
4
5
6
7
8
9
10
11
12
13
func main(){
trace()
}

func trace() {
pc := make([]uintptr, 10) // 生成一个指向10个空间的uintptr指针
n := runtime.Callers(0, pc) // 栈的program counter,放到pc这个指针中
for i := 0; i < n; i++ {
f := runtime.FuncForPC(pc[i]) // 把程序计数器地址对应的函数的信息获取出来
file, line := f.FileLine(pc[i]) // 获取调用函数名称和行号
fmt.Printf("%s:%d %s\n", file, line, f.Name())
}
}

这样就可以看到一连串的程序调用列表了

1
2
3
4
5
/usr/lib/go/src/runtime/extern.go:247 runtime.Callers
/usr/lib/go/src/runtime/extern.go:247 runtime.Callers
/home/nero/GoProgram/Caller-Learn/main.go:10 main.main
/usr/lib/go/src/runtime/proc.go:259 runtime.main
/usr/lib/go/src/runtime/asm_amd64.s:1595 runtime.goexit

func CallersFrames(callers []uintptr) *Frames

Callers() 只是获取了程序计数器,使用 CallersFrames() 可以获取到整个栈的信息
意思大概就是 Callers 只是获取一个指针,需要的通过指针去寻找并获取,而 CallersFrames() 是把所有信息一次性全部拿过来了
CallersFrames() 将获取到的信息放到 callers 指针中,返回一个 pc 的切片指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func main(){
trace()
}

func trace() {
pc := make([]uintptr, 10)
n := runtime.Callers(0, pc)
frames := runtime.CallersFrames(pc[:n]) // 直接获取整个栈信息,放到pc中,返回一个pc切片指针
for {
frame, more := frames.Next()
// 此处解析名字时就不需要使用 FuncForPC,直接调用
fmt.Printf("%s:%d %s\n", frame.File, frame.Line, frame.Function)
if !more {
break
}
}
}

效果一样

1
2
3
4
5
/usr/lib/go/src/runtime/extern.go:247 runtime.Callers
/home/nero/GoProgram/Caller-Learn/main.go:46 main.trace2
/home/nero/GoProgram/Caller-Learn/main.go:9 main.main
/usr/lib/go/src/runtime/proc.go:250 runtime.main
/usr/lib/go/src/runtime/asm_amd64.s:1594 runtime.goexit

敲代码敲着敲着突然卡壳了,不懂这里用的是什么函数,不知道是干什么的 (°ཀ°)
然后就去找这个函数是干什么的,于是一个多小时过去了,总算是大概懂了,也没算浪费时间吧,虽然学习项目的进度又慢了一点 (´A`。)
但学到了新东西就是好,一天学一点嘛 (゚∀゚)