证明了没有内存泄漏,这里 有证据支持。
但是存在一个问题:在 func Fprint(w io.Writer, a ...interface{}) (n int, err error)
中初始化的 p := newPrinter(); p.fmt.init(&p.buf)
返回了没有初始化为零的空闲内存 (切片的基础数组),这可能是由于性能原因而没有被正确初始化 - 我们预期该值应该都是零。
简而言之:
两种解决方法:
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))
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()
三次。这里有趣的部分是第二次调用 my.Write()
显示了一个编译器生成的具有不同切片容量的切片,该切片未初始化为零,“也许这只是一个无害的小问题issue”:
如果您想窥探计算机的内存,请尝试这个:
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)
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)
输出:
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)
,所以模板引擎生成了长度为3、容量为128的 p
。
在调试位于 /usr/local/go/src/fmt/print.go
文件中第230行的第二个代码后,似乎是 fmt.buffer
的长度为3,容量为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)
。
func newPrinter() *pp {
p := ppFree.Get().(*pp)
p.panicking = false
p.erroring = false
p.wrapErrs = false
p.fmt.init(&p.buf)
return p
}
获取并返回未初始化为零的可用内存。
TextNode,ActionNode,TextNode
,并且会写入3次。但是,在你的代码中,在第二次写入时:[]byte("50 22222222")
在局部变量p
中,并且在上一个循环中已经写入了stdout。因此,我不认为这是由于模板使用的切片未初始化造成的。 - Iv4nt.Execute(my, my)
调用func (m *myWriter) Write(p []byte)
,因此由模板引擎生成的p
具有len=3
和cap=128
。 - wasmupAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 64 64 1 1 128 buf: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1
- wasmupmyWriter.Write
时,fmt.Sprint将缓冲区设置为“info {SSSSS}”,然后在第二次调用时,fmt.printer从ppFree sync.Pool
获取缓存,并且不会将缓冲区全部设置为byte(0)。但仍有一些让我困惑的地方:1.如果我在函数index
中调用fmt.Sprintln而不是Write
(https://play.golang.org/p/u6jJDaBHfk5),则fmt.buffer中的内存不会泄漏;2.为什么模板引擎生成的本地变量`p []byte`中读取了fmt.buffer中的内存? - Iv4n"info{SSSSS}"
或者我的代码中的fmt.Println("gen:", no, gen())
是函数调用参数,编译器将其放入堆栈中以调用函数fmt.Sprintln()
或者我的代码中的fmt.Println()
。因此,当函数调用完成(从该函数返回)时,堆栈(内存)占用仍然存在(没有任何问题),新切片的下一个内存会使用它(这是可以的)。但我们期望新的切片被初始化为零(由于性能原因可能没有被正确初始化)。 - wasmup