前言
我想大家都有这么一个场景,在类里实现一个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了,上次因为递归风控计算,没控制好逻辑,直接爆了。 😅