添加到结构体切片时出现无效的内存地址或空指针解引用错误。

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

Invalid memory address or nil pointer dereference when appending to slice of structs

问题

以下是翻译的内容:

package main

import (
	"fmt"
)

type Person struct {
	name string
}

func main() {
	p := make([]*Person, 0)
	p = append(p, &Person{"Brian"})
	fmt.Println(p[0].name)
	p = append(p, &Person{"Le Tu"})
	fmt.Println(p[1].name)
}

上面的代码可以正常工作。

package main

import (
	"fmt"
)

type Person struct {
	name string
}

func main() {
	p := make([]*Person, 1) // 将0改为1
	p = append(p, &Person{"Brian"})
	fmt.Println(p[0].name)
	p = append(p, &Person{"Le Tu"})
	fmt.Println(p[1].name)
}

上面的代码会引发错误。

我对append的理解是它隐藏了扩展/添加的机制。显然,我将append视为切片的一种“推入”方式的心理模型是错误的。有人能解释一下为什么上面的第二个示例会引发错误吗?为什么我不能只是使用append来添加我的结构体?

英文:
package main

import (
	"fmt"
)

type Person struct {
	name	string
}

func main() {
	p := make([]*Person, 0)
	p = append(p, &Person{"Brian"})
	fmt.Println(p[0].name)
	p = append(p, &Person{"Le Tu"})
	fmt.Println(p[1].name)
}

The above works fine.

package main

import (
	"fmt"
)

type Person struct {
	name	string
}

func main() {
	p := make([]*Person, 1) //Changed to 1 instead of 0
	p = append(p, &Person{"Brian"})
	fmt.Println(p[0].name)
	p = append(p, &Person{"Le Tu"})
	fmt.Println(p[1].name)
}

The above panics.

My understanding of append was that it hid the mechanics of extending/adding. Clearly, my mental model of using append as a sort of "push" for slices is incorrect. Can anyone explain to me why the second sample above panics? Why can't I just append my struct?

答案1

得分: 7

例如,

package main

import (
	"fmt"
)

type Person struct {
	name string
}

func main() {
	p := make([]*Person, 1) //将0改为1
	fmt.Println(len(p), p)
	p = append(p, &Person{"Brian"})
	fmt.Println(len(p), p)
	fmt.Println(p[1].name)
	fmt.Println(p[0])
	fmt.Println(p[0].name)
}

输出结果:

1 [<nil>]
2 [<nil> 0x10500190]
Brian
<nil>
panic: runtime error: invalid memory address or nil pointer dereference

在追加之前,p的长度为1,追加之后长度为2。因此,p[0]具有未初始化的指针值nilp[0].name是无效的。

Go编程语言规范

追加和复制切片

可变参数函数append将零个或多个值x追加到类型为S的切片s中,并返回结果切片,类型也为S

指针类型

未初始化指针的值为nil

选择器

以下规则适用于选择器:

  1. 如果x是指针类型且具有值nil,且x.f表示结构字段,则对x.f的赋值或求值会导致运行时恐慌。
英文:

For example,

package main

import (
	&quot;fmt&quot;
)

type Person struct {
	name string
}

func main() {
	p := make([]*Person, 1) //Changed to 1 instead of 0
	fmt.Println(len(p), p)
	p = append(p, &amp;Person{&quot;Brian&quot;})
	fmt.Println(len(p), p)
	fmt.Println(p[1].name)
	fmt.Println(p[0])
	fmt.Println(p[0].name)
}

Output:

1 [&lt;nil&gt;]
2 [&lt;nil&gt; 0x10500190]
Brian
&lt;nil&gt;
panic: runtime error: invalid memory address or nil pointer dereference

p has length 1 before the append, length 2 after. Therefore, p[0] has the uninitialized pointer value nil and p[0].name is invalid.

> The Go Programming Language Specification
>
> Appending to and copying slices
>
> The variadic function append appends zero or more values x to s of
> type S, which must be a slice type, and returns the resulting slice,
> also of type S.
>
> Pointer types
>
> The value of an uninitialized pointer is nil.
>
> Selectors
>
> The following rules apply to selectors:
>
> 4) If x is of pointer type and has the value nil and x.f denotes a struct field, assigning to or evaluating x.f causes a run-time panic.

答案2

得分: 2

make的参考页面

使用make构建切片时,第一个整数参数是创建的切片的实际长度:

p := make([]*Person, 1)
// 等价于
p := []*Person{nil}  // <- 长度为1的切片,单元格包含*Person类型的默认零值

如果你想创建长度为0但具有预定义容量的切片,需要使用make的三个参数版本:

p := make([]*Person, 0, 100)  // <- 长度为0的切片,但前100个append操作不会重新分配切片

一个简单的示例,演示如何使用这两种情况:

// 100个单元格的切片:
p1 := make([]*Person, 100)
for i := 0; i < 100; i++ {
    name := fmt.Sprintf("Foo %d", i+1)
    // 你可以访问并赋值给p1[i],因为p1有100个单元格:
    p1[i] = &Person{name}
}
fmt.Printf("p1[0].Name: %s\n", p1[0].Name)
fmt.Printf("p1[99].Name: %s\n", p1[99].Name)

// 0个单元格,100个容量的切片:
p2 := make([]*Person, 0, 100)
for i := 0; i < 100; i++ {
    name := fmt.Sprintf("Foo %d", i+1)
    // 你无法访问p2[i],因为该单元格尚不存在:
    p2 = append(p2, &Person{name})
}
fmt.Printf("p2[0].Name: %s\n", p2[0].Name)
fmt.Printf("p2[99].Name: %s\n", p2[99].Name)

play.golang链接

英文:

Reference page for make

When using make to build a slice, the first integer argument is the actual length of the created slice :

p := make([]*Person, 1)
// is equivalent to
p := []*Person{nil}  //&lt;- slice of length 1, cells contain the default
                     //   zero value for the *Person type

If you want to create a slice of length 0, but with a predefined capacity, you need to use the 3 arguments version of make :

p := make([]*Person, 0, 100) //&lt;- slice of length 0, but the first 100 append 
                             //   won&#39;t reallocate the slice

A simple example on how to use both cases :

//100-cell slice :
p1 := make([]*Person, 100)
for i := 0; i &lt; 100; i++ {
	name := fmt.Sprintf(&quot;Foo %d&quot;, i+1)
	//you can access and assign to p1[i] because p1 has 100 cells :
	p1[i] = &amp;Person{name}
}
fmt.Printf(&quot;p1[0].Name : %s\n&quot;, p1[0].Name)
fmt.Printf(&quot;p1[99].Name : %s\n&quot;, p1[99].Name)

//0-cell, 100-capacity slice :
p2 := make([]*Person, 0, 100)
for i := 0; i &lt; 100; i++ {
	name := fmt.Sprintf(&quot;Foo %d&quot;, i+1)
	//you cannot access p2[i], the cell doesn&#39;t exist yet :
	p2 = append(p2, &amp;Person{name})
}
fmt.Printf(&quot;p2[0].Name : %s\n&quot;, p2[0].Name)
fmt.Printf(&quot;p2[99].Name : %s\n&quot;, p2[99].Name)

play.golang link

huangapple
  • 本文由 发表于 2014年1月31日 13:58:38
  • 转载请务必保留本文链接:https://go.coder-hub.com/21473436.html
匿名

发表评论

匿名网友

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

确定