Go中指针的常见错误

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

Common mistakes with pointers in Go

问题

我很乐意帮你翻译。以下是翻译好的内容:

我很久没有使用指针了,而且当我使用时,只是在学术环境中使用,现在由于使用了C#/Java/scala,我有点生疏了。

在Golang中,人们常犯的一些指针错误有哪些?

有没有办法测试你是否正确使用了指针?我猜在出现问题之前,检测内存泄漏总是很困难的。

英文:

I haven't used pointers in a long time, and when I did it was only in an academic setting and I am a bit rusty now thanks to C#/Java/scala.

What are some common mistakes people make with pointers in Golang?

Are there ways of testing if you have used them correctly? I guess it is always hard to detect a memory leak until things go wrong.

答案1

得分: 3

鉴于Go语言是垃圾回收的,并且不允许指针算术运算,所以你不太容易犯错。你可以使用unsafe包,但它的名字已经说明了它是不安全的。

nil指针仍然存在。对它们进行解引用将导致恐慌,这在某种程度上类似于C#/Java中的异常-你会得到一个清晰的错误描述和发生错误的堆栈跟踪。

内存泄漏-GC几乎会为你做所有事情,就像在C#/Java中一样。但我知道有一种特殊情况-切片。通常通过创建另一个切片来删除元素,例如:

a = append(a[:i], a[i+1:]...)

这段代码可能会泄漏你删除的元素。这是因为切片在内部是一个包含数组(只是一个指针)、长度和容量的结构体。当你删除一个元素时,新的切片可能仍然包含相同的数组,并且仍然引用你删除的元素。GC不会释放它。为了解决这个问题,在删除之前你需要将元素设置为nil。

还有指针与值方法接收器的混淆。这不是一个错误,更像是你需要做出和理解的设计决策。使用值接收器的方法将获得接收器的副本,它不能修改状态。所以如果你想修改状态,你需要使用指针接收器。另外,如果你的结构体很大,并且不希望在每次调用方法时都进行复制,你可能还想使用指针接收器。

英文:

Given that Go is garbage collected and doesn't allow pointer arithmetics there is not much that you can do wrong. You can use unsafe package for that but it's name speaks for itself - it's unsafe.

nil pointers are still there. Dereferencing them will cause a panic which is somewhat like exceptions in C#/Java - you get a clear error description and a stack trace where it happend.

Memory leaks - GC will do almost everything for you just like in C#/Java. But there is a special case that I know of - slices. Removing an element is usually done by creating another slice like this:

a = append(a[:i], a[i+1:]...)

this code might leak the element you removed. That's because internally slice is a struct that contains an array (just a pointer), length and capacity. When you remove an element new slice might contain the same array and it will still reference the element you removed. GC will not free it. To solve that you need to nil the element before removing it.

And there is also pointer vs value method receivers confusion. It's not a mistake, more like a design decision you have to make and understand. Method with value receiver will get a copy of the receiver, it can't modify the state. So if you want to modify the state then you need pointer receiver. Also if your structs are big and you don't want them to be copied every time you call a methid you also might want to use pointer receivers.

答案2

得分: 0

我发现的常见错误是人们忘记了复杂结构体中的指针。

每个切片、映射和接口都是一个指针。当你有一个包含另一个包含指针的结构体时,你可能只看到 S1{s2: S2},并认为像这样复制结构体是没问题的:a=b,但实际上不是这样的。因为在 s2 内部,一个指针变量,比如说 p,它们的地址被复制了,而不是它们指向的值。当你修改 *a.s2.p 处的值时,*b.s2.p 也会返回相同的值。

这是一个非常简单的示例,很明显存在问题,但在更大的应用程序中可能不太明显。

这个问题也会出现在通道中。如果你发送一个指针,你将在另一端得到相同的指针(指针值的副本,即地址)。在这种情况下,与第一个情况一样,一个安全的解决方案是使用 Clone 函数创建一个克隆对象。你通过通道发送克隆体,发送方不再使用它。

代码示例请参考:http://play.golang.org/p/KQ99KICgbu

英文:

The common mistake I found is that people forget about pointers inside their complicated structs.

Each slice, map, interface is a pointer. When you have a struct containing another struct containing a pointer, you might just see S1{s2: S2}, and you think it's fine to have a struct copied like this: a=b, when actually it's not fine, as inside s2, a poiner vatriable, let's say p, will have their address copied, and not the value to which it points. When yoy modify the value found at *a.s2.p, the *b.s2.p will return the same value.

package main

import (
	"fmt"
)

type S1 struct {
	v int
	s2 S2
}

type S2 struct {
	p *int
}

func main() {
	x := 1
	b := S1{v: 10, s2: S2{p: &x}}
	a := b
	fmt.Printf("a = %+v\nb = %+v\n*a.s2.p = %d\n*b.s2.p = %d\na.s2.p = %p\nb.s2.p = %p\n", a, b, *a.s2.p, *b.s2.p, a.s2.p, b.s2.p)
	*a.s2.p = 5
	fmt.Printf("*a.s2.p = %d\n*b.s2.p = %d\na.s2.p = %p\nb.s2.p = %p\n", *a.s2.p, *b.s2.p, a.s2.p, b.s2.p)
}

http://play.golang.org/p/KQ99KICgbu

This it's a very simple example, and it looks obvious there is an issue, but on bigger applications this might not be that obvious.

This issue appears with channels as well. If you send a pointer, you will get on the other end the same pointer (a copy of your pointer's value, which is an address). In this case, as with the first case, a safe solution is to use a Clone function, to create a cloned object. You send the clone through your channel and don't use it anymore on the sender side.

huangapple
  • 本文由 发表于 2016年4月15日 07:07:45
  • 转载请务必保留本文链接:https://go.coder-hub.com/36635566.html
匿名

发表评论

匿名网友

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

确定