Go 结构体嵌套结构体指针,必须先初始化指针结构体,才能赋值

2024/05/06 Go 踩过的坑 共 2611 字,约 8 分钟

go 结构体嵌套结构体指针,必须先初始化指针结构体,才能赋值

go 结构体嵌套结构体指针,必须先初始化指针结构体,才能赋值

“runtime error: invalid memory address or nil pointer dereference”

奇怪,出现”runtime error: invalid memory address or nil pointer dereference” 的错误原因一般是访问了空指针指向的内存。

func (c *ThriftGeneric) Init(Conf *config.Config) error {
    // Waiting for server reflection
    p, err := generic.NewThriftFileProvider(Conf.IDLPath)
    if err != nil {
        return err
    }

    fmt.Println(Conf)

    c.Provider = p
    // 报错空指针错误的地方
    c.Conf = Conf

    g, err := generic.JSONThriftGeneric(p)
    if err != nil {
        return err
    }
    c.Generic = g

    if err := c.BuildClientOptions(); err != nil {
        return err
    }

    cli, err := genericclient.NewClient(Conf.Service, c.Generic, c.ClientOpts...)
    if err != nil {
        return err
    }
    c.Client = cli
    return nil
}

为啥走到这里报错呢?那就是 c.Conf 是 nil,但是之前一直是没有问题的啊?

后来发现自己有一次重构:

原来:

type ThriftGeneric struct {
    Client      genericclient.Client
    Generic     generic.Generic
    Conf        *config.Config
    ClientOpts  []client.Option
    CallOptions []callopt.Option
    Resp        interface{}
    Provider generic.DescriptorProvider
}

func NewThriftGeneric() Client {
    return &ThriftGeneric{}
}

重构之后:

type GenericClientBase struct {
    Client      genericclient.Client
    Generic     generic.Generic
    Conf        *config.Config
    ClientOpts  []client.Option
    CallOptions []callopt.Option
    Resp        interface{}
}

type ThriftGeneric struct {
    *GenericClientBase
    Provider generic.DescriptorProvider
}

func NewThriftGeneric() Client {
    // 一看这里,不对劲,发现原因了
    // GenericClientBase没有初始化。。
    return &ThriftGeneric{}
}

修改之后就好了。c.GenericClientBase.Confc.Conf 是一样的,所以直接后者赋值的时候不用变。

func NewThriftGeneric() Client {
    return &ThriftGeneric{
        GenericClientBase: &GenericClientBase{},
    }
}

但是这样也是对的:

type ThriftGeneric struct {
    GenericClientBase
    Provider generic.DescriptorProvider
}

func NewThriftGeneric() Client {
    return &ThriftGeneric{}
}

这个可以总结为 go 结构体嵌套结构体指针,必须先初始化指针结构体,才能赋值** 问题**

但是,这两种方式赋值,最后 NewThriftGeneric 结构体的内存布局一样吗?编译器是分别如何处理这两种的呢?这两种更加推荐哪一个呢?这个留个坑,目前我还不会。

推荐使用直接嵌套结构体的方式,因为这样更加简洁和直观。直接嵌套结构体可以避免手动初始化嵌套结构体的步骤,减少了出错的可能性,同时也更容易理解和维护。只有在确实需要使用指针嵌套结构体时,才应该选择这种方式。

再来总结一下可能出现 invalid memory address or nil pointer dereference 的原因

  1. 未初始化的指针: 如果您声明了一个指针变量但没有为其分配内存或者将其初始化为指向某个有效的对象,则当您尝试通过该指针访问对象时会出现空指针错误。
go
Copy code
var p *int
fmt.Println(*p) // 这里会导致空指针错误,因为p尚未指向任何有效的内存地址
  1. 返回空指针的函数: 当函数返回一个未初始化的指针或者 nil 指针时,如果调用者试图使用该指针,就会触发空指针错误。
go
Copy code
func createPointer() *int {var i intreturn &i // 这里返回的是一个局部变量的地址,当函数返回后,该指针将不再有效
}
p := createPointer()
fmt.Println(*p) // 这里会导致空指针错误,因为p指向的对象已经被销毁了
  1. 切片、映射和通道的初始化: 如果切片、映射或通道没有被初始化,或者被赋值为 nil,当您尝试访问它们的元素或者使用它们的方法时,也会触发空指针错误。
go
Copy code
var s []int
fmt.Println(s[0]) // 这里会导致空指针错误,因为切片s尚未被初始化var m map[string]int
fmt.Println(m["key"]) // 这里会导致空指针错误,因为映射m尚未被初始化var ch chan int
ch <- 1 // 这里会导致空指针错误,因为通道ch尚未被初始化
  1. 方法接收者为 nil: 如果您尝试调用一个方法,而该方法的接收者是 nil 指针,则会触发空指针错误。
go
Copy code
type MyStruct struct {}

func (m *MyStruct) Method() {
    fmt.Println("Method called")
}

var ms *MyStruct
ms.Method() // 这里会导致空指针错误,因为ms是一个nil指针

文档信息

Search

    Table of Contents