【问题标题】:why does golang nil pointer dereference happen in submethods为什么golang nil指针取消引用发生在子方法中
【发布时间】:2022-01-23 16:13:20
【问题描述】:

我有这个代码

package main

import "fmt"

type Foo struct {
    Bar string
}

func (f *Foo) level4() {
    fmt.Printf("Bar = %s\n", f.Bar)
}

func (f *Foo) level3() {
    f.level4() // panics here, 2 levels down
}

func (f *Foo) level2() {
    f.level3()
}

func (f *Foo) level1() {
    f.level2()
}

type FooWrapper struct {
    foo *Foo
}

func main() {
    w := FooWrapper{}
    w.foo.level1() // expected it to panic here, since foo is nil
}

正如预期的那样,运行它会给出

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

但是,我预计 nil 指针取消引用会发生在 w.foo.level1(),因为 foonil。相反,它会调用级别 1、2 和 3 并在那里发生恐慌。为什么会这样?

playground link

【问题讨论】:

  • Go 方法本质上是语法糖。 func (f *Foo) level1() 在功能上等同于 func level1(*Foo) 因此,在 nil 指针上调用指针接收器是完全有效的,并且调用方法不会取消引用指针接收器
  • 谢谢丹尼尔。如果不是评论,我会将其标记为已接受的答案,因为它确实回答了我的问题。但是,我仍然不明白为什么它会在 f.level4() 而不是 fmt.Printf("Bar = %s\n", f.Bar) 然后出现恐慌
  • 当它试图确定 f.Bar 的值时,它会在调用 Printf 之前在 level4 内发生恐慌。

标签: go segmentation-fault


【解决方案1】:

为什么会这样?

因为 w.foo.level1() 是有效语句,同样 f.level2()、f.level3()、f.level4() 也是有效语句。

试试这个

func (f *Foo) level1() {
    println("Hello Go")
    f.level2()
}

它将打印 Hello Go 并调用 f.level2() 并查看最后调用级别 4

func (f *Foo) level4() {
    println("Hello Go 4")
    fmt.Printf("Bar = %s\n", f.Bar) 
}

它将打印Hello Go 4,但在下一行显示跟踪时会出现恐慌,或者您可以说出错误的来源

【讨论】:

    【解决方案2】:

    任何指针类型的默认值为nil

    当您使用构建对象时

    w := FooWrapper{}
    

    foo 的值将被分配默认的nil

    要解决问题,您应该传递一个 foo 的值。

    w := FooWrapper{foo: &Foo{Bar: "bar"}}
    

    为了防止这个问题,我通常会做一些构造函数。

    func NewFooWrapper() *FooWrapper {
       return &FooWrapper{foo: &Foo{Bar: "bar"}}
    }
    
    w := NewFooWrapper()
    

    或者如果foo 的值不能被硬编码。

    func NewFooWrapper(foo *Foo) *FooWrapper {
       return &FooWrapper{foo: foo}
    }
    
    w := NewFooWrapper(&Foo{Bar: "bar"})
    

    从编码的角度来看,这使得它更加明确,可以防止在其他代码位置出现这些错误。

    【讨论】:

      【解决方案3】:

      您没有正确初始化 main 函数中的 FooWrapper。您的 foo 类型为 *Foo 的字段在 w 变量类型为 FooWrapper 内是 nil,因为您尚未为其分配值,并且指针类型变量的默认值为 Go 中的 nil

      简单正确的初始化如下

          w := FooWrapper{
              foo: &Foo{
                  Bar: "bar",
              },
          }
      

      playground运行

      输出:

      Bar = bar
      

      恐慌没有出现在level1()level2()level3() 中的原因是它们没有在这些函数中使用任何Foo 的字段。而且在level4() 中发生恐慌不是因为函数调用,而是因为它在打印中使用了f.Bar

      在围棋中,

      如果接口本身的具体值是 nil,则该方法将使用 nil 接收器调用。

      你可以在https://go.dev/tour/methods/12找到一个简单的例子

      您还可以在 Calls 下的 Go 规范文档中找到很好的解释

      如果 x 的(类型)的方法集包含 m 并且参数列表可以分配给 m 的参数列表,则方法调用 x.m() 是有效的。如果 x 是可寻址的且 &x 的方法集包含 m,则 x.m() 是 (&x).m() 的简写

      【讨论】:

      • 不打算初始化 foo 变量。正如我在问题中所说,我想看看它会在什么时候恐慌。 4级没恐慌,3级恐慌。
      • 我认为您没有得到正确的答案。它在level4() 处惊慌失措。但是恐慌跟踪显示为level3(),因为level4() 函数中没有其他东西。如果在level4() 之前在printf 之前添加一些简单的打印,您可以看到实际的恐慌。
      • 如果您使用的是带调试功能的IDE,请一步一步调试,您可以看到究竟是哪里发生了panic。
      猜你喜欢
      • 1970-01-01
      • 2013-04-29
      • 2011-08-08
      • 2011-02-24
      • 1970-01-01
      • 1970-01-01
      • 2020-11-15
      • 2015-02-24
      • 2020-06-05
      相关资源
      最近更新 更多