亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

為什么這個 Golang 代碼會泄漏內存中變量的值

為什么這個 Golang 代碼會泄漏內存中變量的值

Go
HUH函數 2023-07-26 19:37:22
此代碼可能會泄漏內存中變量的值。我想也許fmt.XprintY不會重置緩沖區,但我的調試嘗試是徒勞的。package mainimport (    "bytes"    "fmt"    "io"    "text/template")type SecWriter struct {    w io.Writer}func (s *SecWriter) Write(p []byte) (n int, err error) {    fmt.Println(string(p), len(p), cap(p))    // here    tmp := fmt.Sprintln("info{SSSSSSSSSSSSSSSSSSSSSSSSSSS}")    if tmp == ""{}    s.w.Write(p[:64])    return 64, nil}func index() {    exp := "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA{{1}}"    b := &bytes.Buffer{}    s := &SecWriter{        w: b,    }    t := template.Must(template.New("index").Parse(exp))    t.Execute(s, nil)    fmt.Println("buf: ", b.String())}func main() {    index()}我的go env:set GOARCH=amd64set GOOS=windows去版本go version go1.12.5 windows/amd64輸出是:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 64 641 1 128buf: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1nfo{SSSSSSSSSSSSSSSSSSSSSSSSSSS}                 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA可以看到,內存中變量的部分值:tmp := fmt.Sprintln("info{SSSSSSSSSSSSSSSSSSSSSSSSSSS}")泄漏到緩沖區。
查看完整描述

3 回答

?
繁華開滿天機

TA貢獻1816條經驗 獲得超4個贊

沒有內存泄漏,
但有一個問題p := newPrinter():對initialized的調用p.fmt.init(&p.buf)返回func Fprint(w io.Writer, a ...interface{}) (n int, err error)空閑內存(切片的底層數組),而沒有將其初始化為零(可能是出于性能原因而未初始化 -我們期望全部為零)。

TL;DR:
兩種解決方案:
1. 解決方法:使用s.w.Write(p)代替s.w.Write(p[:64]),或編輯代碼并將其p[len(p):cap(p)]全部設置為零(如果您不這樣做或無法觸及第二個解決方案):

func (s *SecWriter) Write(p []byte) (n int, err error) {

? ? b := p[len(p):cap(p)]

? ? for i := range b {

? ? ? ? b[i] = 0

? ? }

? ? fmt.Println(string(p), len(p), cap(p))

? ? // here

? ? tmp := fmt.Sprintln("info{SSSSSSSSSSSSSSSSSSSSSSSSSSS}")

? ? if tmp == "" {

? ? }

? ? s.w.Write(p[:64])

? ? return 64, nil

}

在 Windows ( C:\Go\src\fmt\format.go) 或 Linux ( /usr/local/go/src/fmt/format.go) 文件中,第 58 行將緩沖區設置為全零:

? ? b := (*buf)[:cap(*buf)]

? ? for i := range b {

? ? ? ? b[i] = 0

? ? }

在這個函數內部:


func (f *fmt) init(buf *buffer) {

? ? b := (*buf)[:cap(*buf)]

? ? for i := range b {

? ? ? ? b[i] = 0

? ? }

? ? f.buf = buf

? ? f.clearflags()

}

應用此功能后的代碼輸出:


AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 64 64

1 1 128

buf:? AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1

長答案:
您正在觀看超出指定長度的切片數據,并且允許您查看切片容量的切片數據。
您可以將:替換m.Writer.Write(p[:8])為:m.Writer.Write(p),這將使您的代碼正常工作。以下代碼顯示將template.Parse()模板標記為 3 部分并調用my.Write()3 次。這里有趣的部分是第二次調用顯示my.Write()編譯器生成的具有不同切片容量的切片,該切片未初始化為零,“也許這是一個無害的小問題”:

如果您想監視計算機的內存,請嘗試以下操作:

package main


import (

? ? "bytes"

? ? "fmt"

? ? "io"

? ? "text/template"

)


func main() {

? ? buf := &bytes.Buffer{}

? ? my := &myWriter{"You", buf}

? ? template.Must(template.New("my").Parse("Hi{{.Name}}Bye.")).Execute(my, my)

? ? fmt.Printf("<<%q>>\n", buf.String())

}

func (m *myWriter) Write(p []byte) (n int, err error) {

? ? fmt.Printf("len=%v cap=%v\t%v %v\n", len(p), cap(p), string(p), p[:cap(p)])

? ? no++

? ? fmt.Println("gen:", no, gen())

? ? m.Writer.Write(p)

? ? // m.Writer.Write(p[:8])

? ? return 8, nil

}


type myWriter struct {

? ? Name string

? ? io.Writer

}


const genLen = 8


func gen() string {

? ? b := [genLen]byte{}

? ? for i := range b {

? ? ? ? b[i] = no

? ? }

? ? return string(b[:])

}


var no = byte(49) //'1'

輸出:


len=2 cap=8 Hi [72 105 0 0 0 0 0 0]

gen: 50 22222222

len=3 cap=64? ? You [89 111 117 58 32 53 48 32 50 50 50 50 50 50 50 50 10 50 32 49 48 53 32 48 32 48 32 48 32 48 32 48 32 48 93 10 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]

gen: 51 33333333

len=4 cap=8 Bye. [66 121 101 46 0 0 0 0]

gen: 52 44444444

<<"HiYouBye.">>

并const genLen = 64嘗試進行以下 有趣的更改:cap=64更改cap=128(這是預期之外的):


輸出:


len=2 cap=8 Hi [72 105 0 0 0 0 0 0]

gen: 50 2222222222222222222222222222222222222222222222222222222222222222

len=3 cap=128? ?You [89 111 117 58 32 53 48 32 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 10 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]

gen: 51 3333333333333333333333333333333333333333333333333333333333333333

len=4 cap=8 Bye. [66 121 101 46 0 0 0 0]

gen: 52 4444444444444444444444444444444444444444444444444444444444444444

<<"HiYouBye.">>

這些t.Execute(my, my)調用由模板引擎生成func (m *myWriter) Write(p []byte)。plen=3cap=128


在調試文件內第 230 行的第二個代碼后/usr/local/go/src/fmt/print.go,它似乎fmt.buffer與length=3, 和cap=128, 在這里:


func Fprint(w io.Writer, a ...interface{}) (n int, err error) {

? ? p := newPrinter()

? ? p.doPrint(a)

? ? n, err = w.Write(p.buf)

? ? p.free()

? ? return

}

調用p := newPrinter()此處初始化p.fmt.init(&p.buf):


// newPrinter allocates a new pp struct or grabs a cached one.

func newPrinter() *pp {

? ? p := ppFree.Get().(*pp)

? ? p.panicking = false

? ? p.erroring = false

? ? p.wrapErrs = false

? ? p.fmt.init(&p.buf)

? ? return p

}

獲取并返回可用內存,而不將其初始化為零。


查看完整回答
反對 回復 2023-07-26
?
繁星coding

TA貢獻1797條經驗 獲得超4個贊

在 Go 中,表達式s.w.Write(p[:64])可以將切片擴展到其長度之外而不會出現錯誤(直到切片的容量)。在本例中,提供的緩沖區長度僅為 1,但您將其擴展為 64(如輸出的第二行所示)。額外的63字節中的內容是未定義的,它恰好是某些方法的輸出fmt。

解決方案是檢查切片的長度。如果你想讓切片萬無一失,保證無法看到超出其長度的內容,可以使用切片的三索引語法來設置其容量,例如p = p[::len(p)].


查看完整回答
反對 回復 2023-07-26
?
汪汪一只貓

TA貢獻1898條經驗 獲得超8個贊

如果直接給變量賦值而不是這樣做fmt.Sprintln,變量就不會泄漏。

代碼: https: //play.golang.org/p/Nz0y_MfDjP1

所以我相信fmt.Sprintln是造成泄漏的原因。該函數調用另一個未導出的函數newPrinter來獲取 a ,printer而該函數又維護它自己的池和緩存。我還不夠深入,但我的猜測是,您手動創建的緩沖區可能會以某種方式在這里重疊/共享。

(如果我發現其他內容,我會更新答案)


查看完整回答
反對 回復 2023-07-26
  • 3 回答
  • 0 關注
  • 184 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號