本文介绍了Go中堆栈与堆结构的分配以及它们与垃圾收集的关系的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我是Go的新手,我正在体验C风格的基于堆栈的编程之间的一些共鸣,其中自动变量位于堆栈上,分配的内存驻留在堆栈上,以及Python风格的基于堆栈的编程 - 编程,其中生活在堆栈上的唯一东西是引用/指向堆上对象的指针。



据我所知,以下两个函数给出了相同的输出:
$ b

  func myFunction()(* MyStructType,error){
var chunk * MyStructType = new(HeaderChunk)

...

return chunk,nil
}


func myFunction()(* MyStructType,error){
var chunk MyStructType

...

return& chunk,nil
}

即分配一个新的结构并返回它。



如果我用C写的,第一个会在堆上放置一个对象,第二个放置它在堆栈上。第一个会返回一个指向堆的指针,第二个会返回一个指向堆栈的指针,这个指针在函数返回的时候会蒸发掉,这是一件坏事。


$ b $如果我用Python(或者除C#之外的其他许多现代语言)编写它,例子2将不可能实现。



我得到Go垃圾收集两个值,所以上述两种形式都很好。



引用:

但是它引发了一些问题。

1 - 在示例1中,struct声明为on堆。示例2呢?在堆栈中声明的方式与它在C中一样,还是在堆中声明?



2 - 如果在堆栈中声明了示例2,它如何在函数返回后保持可用?



3 - 如果示例2实际上是在堆中声明的,那么它是如何通过值而不是通过引用传递的?这种情况下指针的作用是什么?

解决方案

值得注意的是,stack和heap语言规范中的任何地方。你的问题的措辞是...在堆栈中声明和...在堆中声明,但要注意的是,Go声明语法没有提及堆栈或堆。



从技术上说,所有问题的答案都依赖于实现。实际上,有一个堆栈(每个goroutine!)和一个堆栈,有些堆栈和一些堆栈。在某些情况下,编译器遵循严格的规则(如 new new 总是在堆上分配),而在其他情况下,编译器会执行转义分析来决定对象是否可以继续在你的示例2中,转义分析将显示指向结构转义的指针,因此编译器必须分配给堆栈,并且必须将其分配给堆。

结构。我认为Go的当前实现在这种情况下遵循一个严格的规则,那就是,如果地址是从结构的任何部分获取的,那么该结构会在堆上。



对于问题3,我们可能会对术语感到困惑。 Go中的所有内容都是按照价值传递的,没有参考依据。这里你正在返回一个指针值。什么是指针?考虑下面对你的例子的修改:

  type MyStructType struct {} 

func myFunction1()( * MyStructType,错误){
var chunk * MyStructType = new(MyStructType)
// ...
return chunk,nil
}

func myFunction2()(MyStructType,error){
var chunk MyStructType
// ...
return chunk,nil
}

type bigStruct struct {
[1e6] float64
}

func myFunction3()(bigStruct,error){
var chunk bigStruct
// ...
return chunk,nil
}

我修改了myFunction2来返回结构而不是结构的地址。现在比较myFunction1和myFunction2的程序集输出,

  --- prog listmyFunction1--- 
0000 (s.go:5)TEXT myFunction1 + 0(SB),$ 16-24
0001(s.go:6)MOVQ $ type。。MyStructType + 0(SB),(SP)
0002(s.go:6)CALL,runtime.new + 0(SB)
0003(s.go:6)MOVQ 8(SP),AX
0004(s.go:8) MOVQ AX,.noname + 0(FP)
0005(s.go:8)MOVQ $ 0,.noname + 8(FP)
0006(s.go:8)MOVQ $ 0,.noname + 16(FP)
0007(s.go:8)RET,

---编程列表myFunction2---
0008(s.go:11)TEXT myFunction2 + 0(SB),$ 0-16
0009(s.go:12)LEAQ块+ 0(SP),DI
0010(s.go:12)MOVQ $ 0,AX
0011(s.go:14)LEAQ .noname + 0(FP),BX
0012(s.go:14)LEAQ chunk + 0(SP),BX
0013(s.go: 14)MOVQ $ 0,.noname + 0(FP)
0014(s.go:14)MOVQ $ 0,.noname + 8(FP)
0015(s.go:14)RET,

不要担心这里的myFunction1输出与peterSO(优秀)答案不同。我们显然运行了不同的编译器。否则,请参阅我修改myFunction2以返回myStructType而不是* myStructType。对runtime.new的调用消失了,在某些情况下这将是一件好事。继续,虽然,这里是myFunction3,

  ---编程列表myFunction3--- 
0016(s。 go:21)TEXT myFunction3 + 0(SB),$ 8000000-8000016
0017(s.go:22)LEAQ块+ -8000000(SP),DI
0018(s.go:22)MOVQ $ 0 ,AX
0019(s.go:22)MOVQ $ 1000000,CX
0020(s.go:22)REP,
0021(s.go:22)STOSQ,
0022(s.go:24)LEAQ组块+ -8000000(SP),SI
0023(s.go:24)LEAQ .noname + 0(FP),DI
0024(s.go:24 )MOVQ $ 1000000,CX
0025(s.go:24)REP,
0026(s.go:24)MOVSQ,
0027(s.go:24)MOVQ $ 0, +8000000(FP)
0028(s.go:24)MOVQ $ 0,.noname + 8000008(FP)
0029(s.go:24)RET,

仍然没有调用runtime.new,并且它确实可以按值返回一个8MB的对象。它有效,但你通常不会想要。这里指针的一点是避免推送8MB的对象。


I'm new to Go and I'm experiencing a bit of congitive dissonance between C-style stack-based programming where automatic variables live on the stack and allocated memory lives on the heap and and Python-style stack-based-programming where the only thing that lives on the stack are references/pointers to objects on the heap.

As far as I can tell, the two following functions give the same output:

func myFunction() (*MyStructType, error) {
    var chunk *MyStructType = new(HeaderChunk)

    ...

    return chunk, nil
}


func myFunction() (*MyStructType, error) {
    var chunk MyStructType

    ...

    return &chunk, nil
}

i.e. allocate a new struct and return it.

If I'd written that in C, the first one would have put an object on the heap and the second would have put it on the stack. The first would return a pointer to the heap, the second would return a pointer to the stack, which would have evaporated by the time the function had returned, which would be a Bad Thing.

If I'd written it in Python (or many other modern languages except C#) example 2 would not have been possible.

I get that Go garbage collects both values, so both of the above forms are fine.

To quote:

But it raises a couple of questions.

1 - In example 1, the struct is declared on the heap. What about example 2? Is that declared on the stack in the same way it would be in C or does it go on the heap too?

2 - If example 2 is declared on the stack, how does it stay available after the function returns?

3 - If example 2 is actually declared on the heap, how is it that structs are passed by value rather than by reference? What's the point of pointers in this case?

解决方案

It's worth noting that the words "stack" and "heap" do not appear anywhere in the language spec. Your question is worded with "...is declared on the stack," and "...declared on the heap," but note that Go declaration syntax says nothing about stack or heap.

That technically makes the answer to all of your questions implementation dependent. In actuality of course, there is a stack (per goroutine!) and a heap and some things go on the stack and some on the heap. In some cases the compiler follows rigid rules (like "new always allocates on the heap") and in others the compiler does "escape analysis" to decide if an object can live on the stack or if it must be allocated on the heap.

In your example 2, escape analysis would show the pointer to the struct escaping and so the compiler would have to allocate the struct. I think the current implementation of Go follows a rigid rule in this case however, which is that if the address is taken of any part of a struct, the struct goes on the heap.

For question 3, we risk getting confused about terminology. Everything in Go is passed by value, there is no pass by reference. Here you are returning a pointer value. What's the point of pointers? Consider the following modification of your example:

type MyStructType struct{}

func myFunction1() (*MyStructType, error) {
    var chunk *MyStructType = new(MyStructType)
    // ...
    return chunk, nil
}

func myFunction2() (MyStructType, error) {
    var chunk MyStructType
    // ...
    return chunk, nil
}

type bigStruct struct {
    lots [1e6]float64
}

func myFunction3() (bigStruct, error) {
    var chunk bigStruct
    // ...
    return chunk, nil
}

I modified myFunction2 to return the struct rather than the address of the struct. Compare the assembly output of myFunction1 and myFunction2 now,

--- prog list "myFunction1" ---
0000 (s.go:5) TEXT    myFunction1+0(SB),$16-24
0001 (s.go:6) MOVQ    $type."".MyStructType+0(SB),(SP)
0002 (s.go:6) CALL    ,runtime.new+0(SB)
0003 (s.go:6) MOVQ    8(SP),AX
0004 (s.go:8) MOVQ    AX,.noname+0(FP)
0005 (s.go:8) MOVQ    $0,.noname+8(FP)
0006 (s.go:8) MOVQ    $0,.noname+16(FP)
0007 (s.go:8) RET     ,

--- prog list "myFunction2" ---
0008 (s.go:11) TEXT    myFunction2+0(SB),$0-16
0009 (s.go:12) LEAQ    chunk+0(SP),DI
0010 (s.go:12) MOVQ    $0,AX
0011 (s.go:14) LEAQ    .noname+0(FP),BX
0012 (s.go:14) LEAQ    chunk+0(SP),BX
0013 (s.go:14) MOVQ    $0,.noname+0(FP)
0014 (s.go:14) MOVQ    $0,.noname+8(FP)
0015 (s.go:14) RET     ,

Don't worry that myFunction1 output here is different than in peterSO's (excellent) answer. We're obviously running different compilers. Otherwise, see that I modfied myFunction2 to return myStructType rather than *myStructType. The call to runtime.new is gone, which in some cases would be a good thing. Hold on though, here's myFunction3,

--- prog list "myFunction3" ---
0016 (s.go:21) TEXT    myFunction3+0(SB),$8000000-8000016
0017 (s.go:22) LEAQ    chunk+-8000000(SP),DI
0018 (s.go:22) MOVQ    $0,AX
0019 (s.go:22) MOVQ    $1000000,CX
0020 (s.go:22) REP     ,
0021 (s.go:22) STOSQ   ,
0022 (s.go:24) LEAQ    chunk+-8000000(SP),SI
0023 (s.go:24) LEAQ    .noname+0(FP),DI
0024 (s.go:24) MOVQ    $1000000,CX
0025 (s.go:24) REP     ,
0026 (s.go:24) MOVSQ   ,
0027 (s.go:24) MOVQ    $0,.noname+8000000(FP)
0028 (s.go:24) MOVQ    $0,.noname+8000008(FP)
0029 (s.go:24) RET     ,

Still no call to runtime.new, and yes it really works to return an 8MB object by value. It works, but you usually wouldn't want to. The point of a pointer here would be to avoid pushing around 8MB objects.

这篇关于Go中堆栈与堆结构的分配以及它们与垃圾收集的关系的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

10-24 11:24