在Go语言中使用指针有什么意义?

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

What's the point of having pointers in Go?

问题

我知道Go语言中的指针允许修改函数的参数,但如果他们只采用了引用(带有适当的const或mutable限定符),会不会更简单呢?现在我们有指针,对于一些内置类型如映射和通道,隐式地通过引用传递。

我是否漏掉了什么,或者Go语言中的指针只是一种不必要的复杂性?

英文:

I know that pointers in Go allow mutation of a function's arguments, but wouldn't it have been simpler if they adopted just references (with appropriate const or mutable qualifiers). Now we have pointers and for some built-in types like maps and channels implicit pass by reference.

<i>Am I missing something or are pointers in Go just an unnecessary complication?</>

答案1

得分: 39

指针有几个有用的原因。指针允许对内存布局进行控制(影响CPU缓存的效率)。在Go中,我们可以定义一个结构,其中所有成员都在连续的内存中:

<!-- language: lang-golang -->

type Point struct {
  x, y int
}

type LineSegment struct {
  source, destination Point
}

在这种情况下,Point结构被嵌入在LineSegment结构中。但是你并不总是可以直接嵌入数据。如果你想支持二叉树或链表等结构,那么你需要支持某种类型的指针。

<!-- language: lang-golang -->

type TreeNode {
  value int
  left  *TreeNode
  right *TreeNode
}

Java、Python等没有这个问题,因为它不允许你嵌入复合类型,所以没有必要在语法上区分嵌入和指向。

解决Swift/C#结构体问题的Go指针

实现相同功能的一个可能的替代方法是像C#和Swift一样区分structclass。但是这样做有一些限制。虽然你通常可以指定一个函数以inout参数的形式接受一个结构体,以避免复制结构体,但它不允许你存储对结构体的引用(指针)。这意味着当你发现有用时,你永远不能将结构体视为引用类型,例如创建一个池分配器(见下文)。

自定义内存分配器

使用指针,你还可以创建自己的池分配器(这是非常简化的,去掉了很多检查,只是展示原理):

<!-- language: lang-golang -->

type TreeNode {
  value int
  left  *TreeNode
  right *TreeNode
  
  nextFreeNode *TreeNode; // 用于内存分配
}

var pool [1024]TreeNode
var firstFreeNode *TreeNode = &amp;pool[0] 

func poolAlloc() *TreeNode {
    node := firstFreeNode
    firstFreeNode  = firstFreeNode.nextFreeNode
    return node
}

func freeNode(node *TreeNode) {
    node.nextFreeNode = firstFreeNode
    firstFreeNode = node
}

交换两个值

指针还允许你实现swap,即交换两个变量的值:

func swap(a *int, b *int) {
   temp := *a
   *a = *b
   *b = temp
}

结论

Java从未能完全取代C++在像Google这样的系统编程中的地位,部分原因是由于缺乏控制内存布局和使用的能力(缓存未命中会显著影响性能)。Go旨在在许多领域取代C++,因此需要支持指针。

英文:

Pointers are usefull for several reasons. Pointers allow control over memory layout (affects efficiency of CPU cache). In Go we can define a structure where all the members are in contiguous memory:

<!-- language: lang-golang -->

type Point struct {
  x, y int
}

type LineSegment struct {
  source, destination Point
}

In this case the Point structures are embedded within the LineSegment struct. But you can't always embed data directly. If you want to support structures such as binary trees or linked list, then you need to support some kind of pointer.

<!-- language: lang-golang -->

type TreeNode {
  value int
  left  *TreeNode
  right *TreeNode
}

Java, Python etc doesn't have this problem because it does not allow you to embed composite types, so there is no need to syntactically differentiate between embedding and pointing.

Issues with Swift/C# structs solved with Go pointers

A possible alternative to accomplish the same is to differentiate between struct and class as C# and Swift does. But this does have limitations. While you can usually specify that a function takes a struct as an inout parameter to avoid copying the struct, it doesn't allow you to store references (pointers) to structs. This means you can never treat a struct as a reference type when you find that useful e.g. to create a pool allocator (see below).

Custom Memory Allocator

Using pointers you can also create your own pool allocator (this is very simplified with lots of checks removed to just show the principle):

<!-- language: lang-golang -->

type TreeNode {
  value int
  left  *TreeNode
  right *TreeNode
  
  nextFreeNode *TreeNode; // For memory allocation
}

var pool [1024]TreeNode
var firstFreeNode *TreeNode = &amp;pool[0] 

func poolAlloc() *TreeNode {
    node := firstFreeNode
    firstFreeNode  = firstFreeNode.nextFreeNode
    return node
}

func freeNode(node *TreeNode) {
    node.nextFreeNode = firstFreeNode
    firstFreeNode = node
}

Swap two values

Pointers also allows you to implement swap. That is swapping the values of two variables:

func swap(a *int, b *int) {
   temp := *a
   *a = *b
   *b = temp
}

Conclusion

Java has never been able to fully replace C++ for systems programming at places such as Google, in part because performance can not be tuned to the same extend due to the lack of ability to control memory layout and usage (cache misses affect performance significantly). Go has aimed to replace C++ in many areas and thus needs to support pointers.

答案2

得分: 34

我真的很喜欢从https://www.golang-book.com/books/intro/8中提取的示例

func zero(x int) {
    x = 0
}
func main() {
    x := 5
    zero(x)
    fmt.Println(x) // x仍然是5
}

与之相对比的是

func zero(xPtr *int) {
    *xPtr = 0
}
func main() {
    x := 5
    zero(&amp;x)
    fmt.Println(x) // x是0
}
英文:

I really like example taken from https://www.golang-book.com/books/intro/8

func zero(x int) {
    x = 0
}
func main() {
    x := 5
    zero(x)
    fmt.Println(x) // x is still 5
}

as contrasted with

func zero(xPtr *int) {
    *xPtr = 0
}
func main() {
    x := 5
    zero(&amp;x)
    fmt.Println(x) // x is 0
}

答案3

得分: 33

Go被设计成一种简洁、极简主义的语言。因此,它最初只有值和指针。后来,出于必要性考虑,添加了一些引用类型(切片、映射和通道)。


《Go编程语言:语言设计FAQ:为什么映射、切片和通道是引用类型,而数组是值类型?》

“关于这个问题有很多历史。早期,映射和通道在语法上是指针,无法声明或使用非指针实例。此外,我们对数组的工作方式也感到困惑。最终,我们决定严格区分指针和值会使语言更难使用。引入引用类型,包括用于处理数组引用形式的切片,解决了这些问题。引用类型给语言增加了一些令人遗憾的复杂性,但它们对可用性有很大影响:引入引用类型后,Go成为了一种更高效、更舒适的语言。”


快速编译是Go编程语言的一个主要设计目标,但这也有代价。其中一个牺牲品似乎是将变量(除了基本的编译时常量)和参数标记为不可变。有人提出了这个要求,但被拒绝了。


golang-nuts:Go语言。一些反馈和疑问。

“将const添加到类型系统会强制它出现在每个地方,并且如果有变化,就必须在每个地方将其移除。虽然在某种程度上标记对象为不可变可能有一些好处,但我们认为const类型限定符不是正确的方式。”

英文:

Go is designed to be a terse, minimalist language. It therefore started with just values and pointers. Later, by necessity, some reference types (slices, maps, and channels) were added.


The Go Programming Language : Language Design FAQ : Why are maps, slices, and channels references while arrays are values?

"There's a lot of history on that topic. Early on, maps and channels were syntactically pointers and it was impossible to declare or use a non-pointer instance. Also, we struggled with how arrays should work. Eventually we decided that the strict separation of pointers and values made the language harder to use. Introducing reference types, including slices to handle the reference form of arrays, resolved these issues. Reference types add some regrettable complexity to the language but they have a large effect on usability: Go became a more productive, comfortable language when they were introduced."


Fast compilation is a major design goal of the Go programming language; that has its costs. One of the casualties appears to be the ability to mark variables (except for basic compile time constants) and parameters as immutable. It's been requested, but turned down.


golang-nuts : go language. Some feedback and doubts.

"Adding const to the type system forces it to appear everywhere, and
forces one to remove it everywhere if something changes. While there
may be some benefit to marking objects immutable in some way, we don't
think a const type qualifier is to way to go."

答案4

得分: 28

引用不能被重新赋值,而指针可以。这一点使得指针在许多情况下非常有用,而引用则无法使用。

英文:

References cannot be reassigned, while pointers can. This alone makes pointers useful in many situations where references could not be used.

答案5

得分: 1

与其在“Go”上下文中回答这个问题,我更愿意在任何语言的上下文中回答(例如C、C++、Go),这些语言实现了“指针”的概念;同样的推理也适用于“Go”。

通常有两个内存部分用于内存分配:堆内存和栈内存(不包括“全局部分/内存”,因为它会脱离上下文)。

堆内存:大多数语言都使用堆内存:比如Java、C#、Python... 但它带来了一个叫做“垃圾回收”的惩罚,这是一个直接的性能损失。

栈内存:在C、C++、Go、Java等语言中,变量可以在栈内存中分配。栈内存不需要垃圾回收,因此它是堆内存的一个高性能替代方案。

但是存在一个问题:当我们在堆内存中分配一个对象时,我们会得到一个“引用”,这个引用可以传递给“多个方法/函数”,通过这个引用,“多个方法/函数”可以直接读取/更新相同的对象(在堆内存中分配)。遗憾的是,对于栈内存来说,情况并非如此;我们知道,每当将栈变量传递给一个方法/函数时,它是“按值传递”的(例如Java),前提是你有“指针的概念”(就像C、C++、Go的情况一样)。

这就是指针的作用。指针允许“多个方法/函数”读取/更新放置在栈内存中的数据。

> 简而言之,“指针”允许使用“栈内存”而不是堆内存来处理变量/结构/对象,从而避免垃圾回收机制带来的性能损失

引入指针在Go中的另一个原因可能是:Go应该是一种“高效的系统编程语言”,就像C、C++、Rust等一样,并且能够与底层操作系统提供的系统调用平稳地工作,因为许多系统调用API在其原型中使用了指针。

有人可能会争辩说,可以通过在系统调用接口之上引入一个无指针层来实现。是的,这是可以做到的,但是拥有指针就像是非常接近系统调用层,这是一种良好的系统编程语言的特征。

英文:

Rather than answering it in the context of “Go”, I would answer this question in the context of any language (e.g. C, C++, Go) which implements the concept of "pointers"; and the same reasoning can be applied to “Go” as well.

There are typically two memory sections where the memory allocation takes place: the Heap Memory and the Stack Memory (let’s not include “global section/memory” as it would go out of context).

Heap Memory: this is what most of the languages make use of: be it Java, C#, Python… But it comes with a penalty called the “Garbage Collection” which is a direct performance hit.

Stack Memory: variables can be allocated in the stack memory in languages like C, C++, Go, Java. Stack memory doesn’t require garbage collection; hence it is a performant alternative to the heap memory.

But there is a problem: when we allocate an object in the heap memory, we get back a “Reference” which can be passed to “multiple methods/functions” and it is through the reference, “multiple methods/functions” can read/update the same object(allocated in the heap memory) directly. Sadly, the same is not true for the stack memory; as we know whenever a stack variable is passed to a method/function, it is “passed by value”(e.g. Java) provided you have the “concept of pointers”(as in the case of C, C++, Go).

Here is where pointers come into picture. Pointes let “multiple methods/functions” read/update the data which is placed in the stack memory.

> In a nutshell, “pointers” allow the use of “stack memory” instead of the heap memory in order to process variables/structures/objects by “multiple methods/functions”; hence, avoiding performance hit caused by the garbage collection mechanism.

Another reason for introducing pointers in Go could be: Go is ought to be an "Efficient System Programming Language" just like C, C++, Rust etc. and work smoothly with the system calls provided by the underlying Operating System as many of the system call APIs have pointers in their prototype.

One may argue that it can done by introducing a pointer-free layer on top of the system call interface. Yes, it can be done but having pointers would be like acting very close to the system call layer which is trait of a good System Programming Language.

huangapple
  • 本文由 发表于 2009年12月8日 06:47:26
  • 转载请务必保留本文链接:https://go.coder-hub.com/1863460.html
匿名

发表评论

匿名网友

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

确定