为什么这个 Golang 代码会泄漏内存中变量的值

此代码可能会泄漏内存中变量的值。


我想也许fmt.XprintY不会重置缓冲区,但我的调试尝试是徒劳的。


package main


import (

    "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=amd64

set GOOS=windows

去版本


go version go1.12.5 windows/amd64

输出是:


AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 64 64

1 1 128

buf: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1nfo{SSSSSSSSSSSSSSSSSSSSSSSSSSS}                 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

可以看到,内存中变量的部分值:


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

泄漏到缓冲区。


HUH函数
浏览 133回答 3
3回答

繁华开满天机

没有内存泄漏,但有一个问题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 641 1 128buf:  AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1长答案:您正在观看超出指定长度的切片数据,并且允许您查看切片容量的切片数据。您可以将:替换m.Writer.Write(p[:8])为:m.Writer.Write(p),这将使您的代码正常工作。以下代码显示将template.Parse()模板标记为 3 部分并调用my.Write()3 次。这里有趣的部分是第二次调用显示my.Write()编译器生成的具有不同切片容量的切片,该切片未初始化为零,“也许这是一个无害的小问题”:如果您想监视计算机的内存,请尝试以下操作:package mainimport (    "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 = 8func 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 22222222len=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 33333333len=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 2222222222222222222222222222222222222222222222222222222222222222len=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 3333333333333333333333333333333333333333333333333333333333333333len=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}获取并返回可用内存,而不将其初始化为零。

繁星coding

在 Go 中,表达式s.w.Write(p[:64])可以将切片扩展到其长度之外而不会出现错误(直到切片的容量)。在本例中,提供的缓冲区长度仅为 1,但您将其扩展为 64(如输出的第二行所示)。额外的63字节中的内容是未定义的,它恰好是某些方法的输出fmt。解决方案是检查切片的长度。如果你想让切片万无一失,保证无法看到超出其长度的内容,可以使用切片的三索引语法来设置其容量,例如p = p[::len(p)].

汪汪一只猫

如果直接给变量赋值而不是这样做fmt.Sprintln,变量就不会泄漏。代码: https:&nbsp;//play.golang.org/p/Nz0y_MfDjP1所以我相信fmt.Sprintln是造成泄漏的原因。该函数调用另一个未导出的函数newPrinter来获取 a ,printer而该函数又维护它自己的池和缓存。我还不够深入,但我的猜测是,您手动创建的缓冲区可能会以某种方式在这里重叠/共享。(如果我发现其他内容,我会更新答案)
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go