Published: 2020-08-25

Go语言编程踩坑

1 遇到的问题和排查

最近调试代码的时候遇到了类似下面这样的错误:

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x1095520]

通过日志,观察到error的值比较奇怪(前后不一致)。下面以一个示例代码来说明。

2 示例代码及说明


package main

import (
        "errors"
        "fmt"
        "log"
)

// 示例的结构体
type S struct {
        a int
}


// main里的条件判断
func checkCondition() bool {
        return false
}

// main里的一个执行分支
func doOne() (*S, error) {

        return &S{a:1}, nil
}

// main里的另一个执行分支所需要的参数
func getParam() (string, error) {
        return "bad", nil

}

// main里的另一个执行分支
func doAnother(param string) (*S, error) {
        if param == "good" {
                return &S{a:2}, nil
        } else {
                return nil, errors.New("error in doAnother")
        }

}

// 主函数
func main() {
        var (
                value *S
                err   error
        )

        // 检查条件是否满足
        // 在我们的示例中,该条件总是不满足。之所以加上这个,是因为我们需要两个分支,由此产生将同样的返回值value和err在外部声明
        if checkCondition() {
                value, err = doOne()
        } else {
                // 先获取所需参数
                param, err := getParam()
                if err != nil {
                        log.Fatalf("error occurs when getParam:% v\n", err)
                }

                // 执行第二个分支
                value, err = doAnother(param)
        }

        // 一起判断上面两个分支的err
        if err != nil {
                log.Fatalf("error occurs when doThings: %v\n", err)
        }

        // 如果没有err,那么访问value的a字段
        fmt.Println("value filed is", value.a)

}


// 运行结果:

//panic: runtime error: invalid memory address or nil pointer dereference
//[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x109d00b]
//
//goroutine 1 [running]:
//main.main()
///Users/nisen/Projects/ethtools/test/demo.go:70 +0x4b
//exit status 2


// 问题分析:

// 问题出在了 := 赋值符,getParam() 里为了新使用的变量param采用:=来接受值的同时,由于处在else的{}里将err也重新申明了,
// 这就导致了doAnother()返回的error复制到了这个新声明的err里(而不是整个if语句上面定义的error)。
// 这就导致了if语句下面对err检查的时候,doAnother(param)返回的错误值由于超出作用域无法获取到,所以这里的被检查的err是nil,
// 这也是为什么在日志中观察到之前的err是存在的,到检查的时候就变成了nil。
// 这就导致了下面value.a实际上对于nil的解引用访问,导致了错误。

// 这个问题在网上搜索一下遇到的人不少,而且比较难发现,因为它和直觉有些违背。
// 对于操作符:=,如果新变量与那个同名已定义变量不在一个作用域中时,那么golang会重新定义这个变量。
// 有时候,前面统一定义了err,而后由于其他变量新定义的需求才采用了:=,副作用就是将以为统一不会变的err也改变了。
// 再加上跨作用域(if else等),导致错误不能捕捉到。


3 END

Author: Nisen

Email: imnisen@163.com