为什么在Golang的子方法中会发生空指针解引用错误?

huangapple go评论78阅读模式
英文:

why does golang nil pointer dereference happen in submethods

问题

我有这段代码:

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() // 在这里发生恐慌,向下两级
}

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

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

type FooWrapper struct {
	foo *Foo
}

func main() {
	w := FooWrapper{}
	w.foo.level1() // 预期在这里发生恐慌,因为foo是nil
}

按预期,运行这段代码会得到以下结果:

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

然而,我预期空指针解引用会发生在w.foo.level1()处,因为foonil。但实际上,它调用了级别1、2和3,并在那里发生了恐慌。为什么会这样呢?

playground链接

英文:

I have this code

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
}

As expected, running this gives

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

However, I expected the nil pointer dereference to happen at w.foo.level1(), since foo is nil. Instead, it calls levels 1, 2 and 3 and panics there. Why is this the case?

playground link

答案1

得分: 1

为什么会这样呢?

因为w.foo.level1()是有效的语句,类似地,f.level2()、f.level3()和f.level4()也是有效的语句。

试试这个:

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

它会打印出"Hello Go",并调用f.level2(),还可以看到最后一次调用level4:

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

它会打印出"Hello Go 4",但在下一行会出现panic,显示错误的追踪信息,或者你可以说是错误的起源。

英文:

Why is this the case?

Because w.foo.level1() is valid statement and similarly f.level2(), f.level3(), f.level4() are also a valid statement.

try this

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

it will print Hello Go and call f.level2() and also see last call level 4

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

it will print Hello Go 4 but panic on next line it is showing the trace or you can say origin of error

答案2

得分: 0

任何指针类型的默认值都是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"})

从编码的角度来看,这样更加明确,可以防止在代码的其他地方出现这些错误。

英文:

The default value of any pointer type is nil.

As you are constructing your object using

w := FooWrapper{}

The value of foo will be assigned the default nil.

To resolve the problem you should pass a value for foo.

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

What I usually do to prevent this issue is to make some constructor function.

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

w := NewFooWrapper()

Or in case the value of foo can't be hardcoded.

func NewFooWrapper(foo *Foo) *FooWrapper {
   return &FooWrapper{foo: foo}
}

w := NewFooWrapper(&Foo{Bar: "bar"})

This makes it more explicit from a coding point of view preventing these errors in other places of code.

答案3

得分: -1

你在主函数中没有正确初始化FooWrapper。在FooWrapper类型的变量w中,foo字段的类型是*Foo,但是它的值是nil,因为你没有为它赋值,而指针类型变量的默认值在Go中是nil

正确的初始化方式如下:

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

playground中运行,输出结果为:

Bar = bar

level1()level2()level3()中没有触发panic的原因是因为它们没有使用Foo的字段。而在level4()中,panic的原因不是函数调用,而是在打印语句中使用了f.Bar

在Go中,

如果接口本身的具体值为nil,则会使用nil接收者调用方法。

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

在Go规范文档的Calls部分也有对此的详细解释。

如果x是可寻址的,并且&x的方法集包含m,则x.m()是(&x).m()的简写形式。

英文:

You did not initialize the FooWrapper in your main function correctly. Your foo field with type *Foo is nil inside w variable type of FooWrapper because you have not assigned a value for that and the default value for a pointer type variable is nil in Go

Simple correct initialization as follows

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

run in playground

output :

Bar = bar

The reason that panic doesn't come in level1(), level2(), level3() is because they are not using any Foo's fields inside those functions. And also in level4() panic occurs not because of the function call, but because it's using f.Bar in print.

In Go,

> If the concrete value inside the interface itself is nil, the method will be called with a nil receiver.

You can find a simple example for this in https://go.dev/tour/methods/12

And also you can find a great explanation for this in Go specs documentation under Calls

> A method call x.m() is valid if the method set of (the type of) x contains m and the argument list can be assigned to the parameter list of m. If x is addressable and &x's method set contains m, x.m() is shorthand for (&x).m()

huangapple
  • 本文由 发表于 2021年12月22日 20:24:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/70449018.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定