前言
我想大家都有这么一个场景,在类里实现一个string方法,供调用方去格式化输出。但如果你的string()里包含了类本身,会引起golang stack overflow.
该文章后续仍在不断的更新修改中, 请移步到原文地址 http://xiaorui.cc/?p=5909
问题重现:
下面是我的问题代码
// xiaorui.cc
package main
import (
"fmt"
)
type people struct {
Name string
}
func (p *people) std() {
fmt.Printf("print: %v", p)
}
func (p *people) String() string {
return fmt.Sprintf("print: %v", p)
}
func main() {
p := &people{}
p.std()
}
运行会报错,终端错误信息如下:
// xiaorui.cc
runtime: goroutine stack exceeds 1000000000-byte limit
fatal error: stack overflow
runtime stack:
runtime.throw(0x10c53c2, 0xe)
/usr/local/go/src/runtime/panic.go:608 +0x72
runtime.newstack()
/usr/local/go/src/runtime/stack.go:1008 +0x729
runtime.morestack()
/usr/local/go/src/runtime/asm_amd64.s:429 +0x8f
// xiaorui.cc
goroutine 1 [running]:
fmt.Sprintf(0x10c49ac, 0x9, 0xc0240003a0, 0x1, 0x1, 0xc00949ae90, 0xc0240003d0)
/usr/local/go/src/fmt/print.go:201 +0xdd fp=0xc024000368 sp=0xc024000360 pc=0x108adbd
main.(*people).String(0xc00000e1e0, 0x10cad08, 0xc00949c300)
/Users/ruifengyun/test/p.go:16 +0x70 fp=0xc0240003c0 sp=0xc024000368 pc=0x1093070
fmt.(*pp).handleMethods(0xc00949c300, 0x76, 0xc00949c301)
/usr/local/go/src/fmt/print.go:603 +0x27c fp=0xc024000450 sp=0xc0240003c0 pc=0x108da8c
fmt.(*pp).printArg(0xc00949c300, 0x10ab4a0, 0xc00000e1e0, 0x76)
/usr/local/go/src/fmt/print.go:686 +0x203 fp=0xc0240004e8 sp=0xc024000450 pc=0x108ded3
fmt.(*pp).doPrintf(0xc00949c300, 0x10c49ac, 0x9, 0xc024000660, 0x1, 0x1)
/usr/local/go/src/fmt/print.go:1003 +0x166 fp=0xc0240005d0 sp=0xc0240004e8 pc=0x1091bc6
fmt.Sprintf(0x10c49ac, 0x9, 0xc024000660, 0x1, 0x1, 0xc00949ae40, 0xc024000690)
/usr/local/go/src/fmt/print.go:203 +0x66 fp=0xc024000628 sp=0xc0240005d0 pc=0x108ad46
main.(*people).String(0xc00000e1e0, 0x10cad08, 0xc00949c240)
/Users/ruifengyun/test/p.go:16 +0x70 fp=0xc024000680 sp=0xc024000628 pc=0x1093070
fmt.(*pp).handleMethods(0xc00949c240, 0x76, 0xc00949c201)
/usr/local/go/src/fmt/print.go:603 +0x27c fp=0xc024000710 sp=0xc024000680 pc=0x108da8c
fmt.(*pp).printArg(0xc00949c240, 0x10ab4a0, 0xc00000e1e0, 0x76)
/usr/local/go/src/fmt/print.go:686 +0x203 fp=0xc0240007a8 sp=0xc024000710 pc=0x108ded3
fmt.(*pp).doPrintf(0xc00949c240, 0x10c49ac, 0x9, 0xc024000920, 0x1, 0x1)
/usr/local/go/src/fmt/print.go:1003 +0x166 fp=0xc024000890 sp=0xc0240007a8 pc=0x1091bc6
fmt.Sprintf(0x10c49ac, 0x9, 0xc024000920, 0x1, 0x1, 0xc00949adf0, 0xc024000950)
/usr/local/go/src/fmt/print.go:203 +0x66 fp=0xc0240008e8 sp=0xc024000890 pc=0x108ad46
main.(*people).String(0xc00000e1e0, 0x10cad08, 0xc00949c180)
/Users/ruifengyun/test/p.go:16 +0x70 fp=0xc024000940 sp=0xc0240008e8 pc=0x1093070
fmt.(*pp).handleMethods(0xc00949c180, 0x76, 0xc00949c101)
/usr/local/go/src/fmt/print.go:603 +0x27c fp=0xc0240009d0 sp=0xc024000940 pc=0x108da8c
fmt.(*pp).printArg(0xc00949c180, 0x10ab4a0, 0xc00000e1e0, 0x76)
/usr/local/go/src/fmt/print.go:686 +0x203 fp=0xc024000a68 sp=0xc0240009d0 pc=0x108ded3
fmt.(*pp).doPrintf(0xc00949c180, 0x10c49ac, 0x9, 0xc024000be0, 0x1, 0x1)
什么原因?
我们直接看golang的fmt源码,文件 go/src/fmt/print.go 。我们的类有string方法,fmt的stringer接口也有个string方法,可以说我们间接的实现了stringer接口,所以递归了。
// xiaorui.cc
type Stringer interface {
String() string
}
func (p *pp) handleMethods(verb rune) (handled bool) {
if p.fmt.sharpV {
if stringer, ok := p.arg.(GoStringer); ok {
handled = true
defer p.catchPanic(p.arg, verb, "GoString")
// Print the result of GoString unadorned.
p.fmt.fmtS(stringer.GoString())
return
}
} else {
switch verb {
case 'v', 's', 'x', 'X', 'q':
switch v := p.arg.(type) {
case error:
handled = true
defer p.catchPanic(p.arg, verb, "Error")
p.fmtString(v.Error(), verb)
return
case Stringer:
handled = true
defer p.catchPanic(p.arg, verb, "String")
// 这里
p.fmtString(v.String(), verb)
return
}
}
}
return false
} 怎么解决?
换个string这个函数名,这个跟fmt的stringer interface冲突了。 或者不要直接把类传进去,而是把属性传递进去。
总结:
这个fmt引起的stack overflow有点奇葩。这是第二次遇到stack overflow了,上次因为递归风控计算,没控制好逻辑,直接爆了。 😅
