sinomoe / sino.moe

just a simple issue blog
http://sino.moe
0 stars 1 forks source link

golang中defer语句的小坑 #5

Open sinomoe opened 6 years ago

sinomoe commented 6 years ago

最近在看net/http的源码巩固golang知识,使用了一些基本特性写了一个http下载工具,在网络不好时总是会报以下错误

2018/05/14 asm_amd64.s:573: goroutine 0 closed
panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xc0000005 code=0x0 addr=0x40 pc=0x5d988e]

goroutine 5 [running]:
main.download(0x66b1d5, 0x63, 0x66b212, 0x26, 0x0, 0x0, 0x68a720, 0xc04206eae0)
        D:/golang/src/github.com/sino2322/playground/downloader.go:14 +0x8e
main.main.func1(0xc042008360, 0xc04204e120, 0xc0420cc620, 0xc042008370, 0xc04204e180, 0x0)
        D:/golang/src/github.com/sino2322/playground/main.go:48 +0x4ce
created by main.main
        D:/golang/src/github.com/sino2322/playground/main.go:30 +0x1ce

顺着错误信息找到了错误提示处的代码

res, err := client.Get(url)
defer res.Body.Close()
if err != nil {
    return 0, err
}

乍一眼好像没错,参考这条解释后才恍然大悟。

根据net/http的文档描述,When err is nil, resp always contains a non-nil resp.Body,由于此处报错invalid memory address or nil pointer dereference,是由于网络原因导致返回err进而导致res为nil,并且,由于defer只是延时函数调用,域和方法都是直接访问的,这样就导致了错误的访问。

解决方法就是在defer res.Body.Close()之前处理错误并返回。修改后的代码如下:

res, err := client.Get(url)
if err != nil {
    return 0, err
}
defer res.Body.Close()

defer注意事项

defer的函数在执行时就会对参数进行求值,并不是等到被调用时才求值。下面的例子将有助于理解。

func trace(s string) string {
    fmt.Println("entering:", s)
    return s
}

func un(s string) {
    fmt.Println("leaving:", s)
}

func a() {
    defer un(trace("a")) // 会对参数进行求值,所以运行到此处立即执行 trace("a")
    fmt.Println("in a")
}

func b() {
    defer un(trace("b")) // 会对参数进行求值,所以运行到此处立即执行 trace("b")
    fmt.Println("in b")
    a()
}

func main() {
    b()
}

打印结果为

entering: b
in b
entering: a
in a
leaving: a
leaving: b