概述
从刚上手golang时就经常遇到这个错误, 三个月之后对golang有些了解, 开始理清一下原因.
问题原因
var p *Obj
某个指针变量声明之后, 还没有经过赋值时默认指向nil, 直接调用指针就会报错
runtime error: invalid memory address or nil pointer dereference
示例
package main
import(
"fmt"
"math"
)
type Point struct {
X, Y float64
}
func (p *Point) Abs() float64 {
return math.Sqrt(p.X*p.X + p.Y*p.Y)
}
func main() {
var p Point //正常
fmt.Println(p) //输出 {0 0}
var nilPtr *Point //报错
fmt.Println(nilPtr) //<nil>
var newPtr *Point = new(Point)
fmt.Println(newPtr) //&{0 0}
//调用Abs()
fmt.Println(p.Abs()) //正常
// fmt.Println(nilPtr.Abs()) //报错
// fmt.Println(newPtr.Abs()) //正常
}
代码解释
- Point结构有一个Abs()方法, 在go中method的receiver为"*Point"指针类型时, Point对象和对象指针皆可调用Abs()方法.
- var p Point 声明, p被初始化, 并赋初始值, 可调用Abs()
- var nilPtr *Point声明, nilPtr指向nil, 未初始化, 不可调用Abs()
- var newPtr *Point = new(Point)声明, newPtr 指向一个被初始化的Point 对象, 可调用Abs().
实际出错场景
package main
import(
"fmt"
"net/http"
)
var HttpServer *http.Server
func StartHttpServer() {
HttpServer := newHttpServer() //改为 HttpServer = newHttpServer()正常
...
}
func StopHttpServer() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := HttpServer.Shutdown(ctx); err != nil { //报错位置
log.Logger.Error("shutdown http server fail", zap.Error(err))
}
}
func main(){
StartHttpServer()
...
StopHttpServer()
}
代码解释
- HttpServer为全局变量, 主函数调用StartHttpServer()完成初始化. StopHttpServer()中直接使用全局变量HttpServer.
- 报错原因: HttpServer.Shutdown()调用报错,访问了nil指针.
- 因为StartHttpServer()中使用了":="进行初始化, 会导致StartHttpServer中的HttpServer是一个新的变量, 且仅在作用在函数体内.
- StopHttpServer()中调用逻辑相当于
var HttpServer *http.Server //当前指向nil
HttpServer.Shutdown()
总结
基本上都是
var HttpServer *http.Server
类似这样的声明语句但是没有完成初始化就调用对象方法或者访问对象变量, 检查代码中是否有这类问题即可.