背景是在寫個日誌庫,日誌庫有個很重要的功能就是要打印出調用棧,知道具體是哪一個文件,哪一個函數調用的Info 等。 而後在測試中發現了一種寫法,我本身本機測試一直ok, 可是業務使用的時候調用棧始終不對,打的調用棧少了一層。莫名其妙的,後來對比發現,咱們就是go version 不同。git
go version :github
go version go1.9.2 darwin/amd64
go env:golang
GOARCH="amd64" GOBIN="" GOEXE="" GOHOSTARCH="amd64" GOHOSTOS="darwin" GOOS="darwin" GOPATH="/Users/didi/Desktop/didi" GORACE="" GOROOT="/usr/local/go1.9.2" GOTOOLDIR="/usr/local/go1.9.2/pkg/tool/darwin_amd64" GCCGO="gccgo" CC="clang" GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/2w/tt1p_4td3yq9xlbl7c2t4jn00000gn/T/go-build427754844=/tmp/go-build -gno-record-gcc-switches -fno-common" CXX="clang++" CGO_ENABLED="1" CGO_CFLAGS="-g -O2" CGO_CPPFLAGS="" CGO_CXXFLAGS="-g -O2" CGO_FFLAGS="-g -O2" CGO_LDFLAGS="-g -O2" PKG_CONFIG="pkg-config"
個人示例代碼是這樣的:bash
package main import ( "fmt" "runtime" ) var i []byte type AAA struct { } func (a *AAA) test1() *AAA { buf := make([]byte, 1<<20) runtime.Stack(buf, true) fmt.Printf("\n%s", buf) return a } func (a *AAA) test2() *AAA { i = append(i, "test2"...) return a } func test() { a := AAA{} a.test1().test2() } func main() { test() }
而後呢,我指望的結果:app
goroutine 1 [running]: main.(*AAA).test1(0xc420045f60, 0x1003a4c) /Users/didi/Desktop/didi/src/test/testCall/main.go:15 +0x87 main.test() /Users/didi/Desktop/didi/src/test/testCall/main.go:27 +0x2f main.main() /Users/didi/Desktop/didi/src/test/testCall/main.go:31 +0x20
可是真實結果是這樣的:函數
goroutine 1 [running]: main.(*AAA).test1(0xc42003df48, 0xc42003df70) /Users/didi/Desktop/didi/src/test/testCall/main.go:15 +0x87 main.(*AAA).test2(...) /Users/didi/Desktop/didi/src/test/testCall/main.go:27 main.test() /Users/didi/Desktop/didi/src/test/testCall/main.go:27 +0x2f main.main() /Users/didi/Desktop/didi/src/test/testCall/main.go:31 +0x20
問題來了,我日誌庫封裝要是有這種相似邏輯,那打印的日誌全都是有問題的,怎麼多是test2調用test1? 莫名其妙的。。。測試
初步懷疑是內聯引發的問題,這裏現象看着很像。編譯,加上不容許內聯後,問題解決, 解決方式蠻簡單的,函數前加個 // go:noinline。ui
爲何會出現這種讓人困惑的現象,經過查看go 官方issue 和 release note 發現下面解釋:debug
Users of runtime.Callers should avoid directly inspecting the resulting PC slice and instead use runtime.CallersFrames to get a complete view of the call stack, or runtime.Caller to get information about a single caller. This is because an individual element of the PC slice cannot account for inlined frames or other nuances of the call stack. // 使用runtime.Caller 不能顯示內聯的細微區別。 Specifically, code that directly iterates over the PC slice and uses functions such as runtime.FuncForPC to resolve each PC individually will miss inlined frames. To get a complete view of the stack, such code should instead use CallersFrames. Likewise, code should not assume that the length returned by Callers is any indication of the call depth. It should instead count the number of frames returned by CallersFrames. Code that queries a single caller at a specific depth should use Caller rather than passing a slice of length 1 to Callers. runtime.CallersFrames has been available since Go 1.7, so code can be updated prior to upgrading to Go 1.9.
而後官方有人提了這個issue, https://github.com/golang/go/issues/22916。總結就是,官方在1.9 的時候以爲1.8及之前版本的不對,Caller 應該將內聯棧也算進去。而後後來你們以爲這種使用不符合習慣,在1.10 又改回去了。我我的試了下,1.10.x, 1.11.x 都是正常的。3d
這種問題,大多數人應該遇不上,一個是要求鏈式調用的寫法,第二個得關心調用棧,纔會遇到這種奇怪現象。