在Go语言中,结构体的堆栈分配和堆分配以及它们与垃圾回收的关系。

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

Stack vs heap allocation of structs in Go, and how they relate to garbage collection

问题

我在C风格的基于堆栈的编程和Python风格的基于堆栈的编程之间有一些认知上的不一致。在C风格的编程中,自动变量存在于堆栈上,而分配的内存存在于堆上;而在Python风格的编程中,只有指向堆上对象的引用/指针存在于堆栈上。

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

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

	...

	return chunk, nil
}


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

	...

	return &chunk, nil
}

也就是说,它们都会分配一个新的结构体并返回它。

如果我用C语言编写这段代码,第一个函数会将一个对象放在堆上,而第二个函数会将其放在堆栈上。第一个函数会返回指向堆上的指针,而第二个函数会返回指向堆栈上的指针,但在函数返回之后,堆栈上的指针会消失,这将是一个糟糕的事情。

如果我用Python(或许多其他现代语言,除了C#)编写它,第二个例子将不可能实现。

我知道Go语言会对这两个值进行垃圾回收,所以上述两种形式都是可以的。

引用一下:

> 请注意,与C不同,返回局部变量的地址是完全可以的;与函数返回后变量关联的存储空间会继续存在。实际上,获取复合字面量的地址每次都会分配一个新的实例,因此我们可以将这两行代码合并起来。
>
> http://golang.org/doc/effective_go.html#functions

但是这也引发了一些问题。

  1. 在第一个例子中,结构体在堆上声明。那么第二个例子呢?它是以与C相同的方式在堆栈上声明的,还是也放在了堆上?

  2. 如果第二个例子是在堆栈上声明的,那么它在函数返回后如何保持可用?

  3. 如果第二个例子实际上是在堆上声明的,那么结构体是按值传递而不是按引用传递的吗?在这种情况下,指针的作用是什么?

英文:

I'm experiencing a bit of cognitive dissonance between C-style stack-based programming, where automatic variables live on the stack and allocated memory lives on the heap, 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:

> Note that, unlike in C, it's perfectly OK to return the address of a
> local variable; the storage associated with the variable survives
> after the function returns. In fact, taking the address of a composite
> literal allocates a fresh instance each time it is evaluated, so we
> can combine these last two lines.
>
> http://golang.org/doc/effective_go.html#functions

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?

答案1

得分: 236

值得注意的是,在语言规范中并没有出现“stack”和“heap”这两个词。你的问题中使用了“...在堆栈上声明”,“...在堆上声明”的措辞,但是请注意,Go的声明语法并没有提到堆栈或堆。

从技术上讲,这使得所有问题的答案都依赖于具体的实现。当然,在实际情况下,每个goroutine都有一个堆栈和一个堆,有些东西放在堆栈上,有些放在堆上。在某些情况下,编译器遵循严格的规则(比如“new总是在堆上分配内存”),在其他情况下,编译器会进行“逃逸分析”来决定一个对象是否可以存在于堆栈上,还是必须在堆上分配内存。

在你的第二个例子中,逃逸分析会显示指向结构体的指针逃逸,因此编译器必须分配结构体。然而,我认为当前的Go实现在这种情况下遵循了一个严格的规则,即如果结构体的任何部分的地址被取出,那么结构体将被分配在堆上。

对于问题3,我们可能会对术语产生困惑。在Go中,所有东西都是按值传递的,没有按引用传递。在这里,你返回了一个指针值。指针的作用是什么?考虑一下你的例子的修改:

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
}

我修改了myFunction2,使其返回结构体而不是结构体的地址。现在比较一下myFunction1和myFunction2的汇编输出:

--- 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     ,

不要担心,这里的myFunction1的输出与peterSO(出色的)答案中的不同。我们显然在运行不同的编译器。否则,看到我将myFunction2修改为返回myStructType而不是*myStructType。对runtime.new的调用消失了,在某些情况下这可能是一件好事。然而,等一下,这是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     ,

仍然没有调用runtime.new,是的,通过值返回一个8MB的对象确实可以工作。它可以工作,但通常你不会想这样做。在这里使用指针的目的是避免传递8MB的对象。

英文:

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.

答案2

得分: 75

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
}

在这两种情况下,Go的当前实现将在堆上为类型为MyStructType的结构体分配内存并返回其地址。这两个函数是等价的;编译器的汇编源代码是相同的。

--- 程序列表 "myFunction1" ---
0000 (temp.go:9) TEXT myFunction1+0(SB),$8-12
0001 (temp.go:10) MOVL $type."".MyStructType+0(SB),(SP)
0002 (temp.go:10) CALL ,runtime.new+0(SB)
0003 (temp.go:10) MOVL 4(SP),BX
0004 (temp.go:12) MOVL BX,.noname+0(FP)
0005 (temp.go:12) MOVL $0,AX
0006 (temp.go:12) LEAL .noname+4(FP),DI
0007 (temp.go:12) STOSL ,
0008 (temp.go:12) STOSL ,
0009 (temp.go:12) RET ,

--- 程序列表 "myFunction2" ---
0010 (temp.go:15) TEXT myFunction2+0(SB),$8-12
0011 (temp.go:16) MOVL $type."".MyStructType+0(SB),(SP)
0012 (temp.go:16) CALL ,runtime.new+0(SB)
0013 (temp.go:16) MOVL 4(SP),BX
0014 (temp.go:18) MOVL BX,.noname+0(FP)
0015 (temp.go:18) MOVL $0,AX
0016 (temp.go:18) LEAL .noname+4(FP),DI
0017 (temp.go:18) STOSL ,
0018 (temp.go:18) STOSL ,
0019 (temp.go:18) RET ,

调用

在函数调用中,函数值和参数按照通常的顺序进行求值。在它们求值之后,调用的函数的参数按值传递给函数,并且被调用的函数开始执行。函数的返回参数按值传递回调用函数,当函数返回时。

所有的函数和返回参数都是按值传递的。返回类型为*MyStructType的返回参数是一个地址。

英文:
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
}

In both cases, current implementations of Go would allocate memory for a struct of type MyStructType on a heap and return its address. The functions are equivalent; the compiler asm source is the same.

--- prog list "myFunction1" ---
0000 (temp.go:9) TEXT    myFunction1+0(SB),$8-12
0001 (temp.go:10) MOVL    $type."".MyStructType+0(SB),(SP)
0002 (temp.go:10) CALL    ,runtime.new+0(SB)
0003 (temp.go:10) MOVL    4(SP),BX
0004 (temp.go:12) MOVL    BX,.noname+0(FP)
0005 (temp.go:12) MOVL    $0,AX
0006 (temp.go:12) LEAL    .noname+4(FP),DI
0007 (temp.go:12) STOSL   ,
0008 (temp.go:12) STOSL   ,
0009 (temp.go:12) RET     ,

--- prog list "myFunction2" ---
0010 (temp.go:15) TEXT    myFunction2+0(SB),$8-12
0011 (temp.go:16) MOVL    $type."".MyStructType+0(SB),(SP)
0012 (temp.go:16) CALL    ,runtime.new+0(SB)
0013 (temp.go:16) MOVL    4(SP),BX
0014 (temp.go:18) MOVL    BX,.noname+0(FP)
0015 (temp.go:18) MOVL    $0,AX
0016 (temp.go:18) LEAL    .noname+4(FP),DI
0017 (temp.go:18) STOSL   ,
0018 (temp.go:18) STOSL   ,
0019 (temp.go:18) RET     ,

> Calls
>
> In a function call, the function value and arguments are evaluated in
> the usual order. After they are evaluated, the parameters of the call
> are passed by value to the function and the called function begins
> execution. The return parameters of the function are passed by value
> back to the calling function when the function returns.

All function and return parameters are passed by value. The return parameter value with type *MyStructType is an address.

答案3

得分: 40

根据Go的常见问题解答

> 如果编译器无法证明变量在函数返回后不会被引用,那么编译器必须将变量分配在垃圾回收堆上,以避免悬空指针错误。

英文:

According to Go's FAQ:

> if the compiler cannot prove that the variable is not referenced after
> the function returns, then the compiler must allocate the variable on
> the garbage-collected heap to avoid dangling pointer errors.

答案4

得分: 13

你不总是知道你的变量是在堆栈上分配还是在堆上分配。
...
如果你需要知道你的变量在哪里分配,可以将"-m" gc标志传递给"go build"或"go run"(例如,go run -gcflags -m app.go)。

来源:http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/index.html#stack_heap_vars

英文:

> You don't always know if your variable is allocated on the stack or heap.
> ...
> If you need to know where your variables are allocated pass the "-m" gc flag to "go build" or "go run" (e.g., go run -gcflags -m app.go).

<sub>Source: http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/index.html#stack_heap_vars&lt;/sub>

答案5

得分: 1

这里是关于堆栈、堆和GC的另一个讨论,可以在A Guide to the Go Garbage Collector中找到。

Go值的存储位置

  • 堆栈分配
    • 存储在局部变量中的非指针Go值可能根本不会由Go GC管理,而是Go会安排分配与其创建的词法作用域相关联的内存。一般来说,这比依赖GC更高效,因为Go编译器能够预先确定何时可以释放该内存并生成清理的机器指令。通常,我们将以这种方式为Go值分配内存称为“堆栈分配”,因为该空间存储在goroutine堆栈上。
  • 堆分配
    • 无法通过这种方式分配内存的Go值,因为Go编译器无法确定其生命周期,被称为“逃逸到堆”。可以将“堆”视为内存分配的综合,用于存放Go值。在堆上分配内存的行为通常称为“动态内存分配”,因为编译器和运行时对于如何使用这块内存以及何时可以清理它几乎没有任何假设。这就是GC的作用:它是一个专门识别和清理动态内存分配的系统。

Go值可能需要逃逸到堆的原因有很多。一个原因可能是其大小是动态确定的。例如,考虑由变量而不是常量确定初始大小的切片的后备数组。请注意,逃逸到堆也必须是传递的:如果对已确定逃逸的Go值的引用被写入到另一个Go值中,那个值也必须逃逸。


逃逸分析

关于如何访问Go编译器逃逸分析的信息,最简单的方法是通过Go编译器支持的调试标志,以文本格式描述它应用或未应用于某个包的所有优化。这包括值是否逃逸。尝试以下命令,其中[package]是某个Go包的路径。

$ go build -gcflags=-m=3 [package]

特定于实现的优化

Go GC对活动内存的统计数据非常敏感,因为复杂的对象和指针图既限制了并行性,又为GC生成了更多的工作。因此,GC包含了一些针对特定常见结构的优化。下面列出了对性能优化最直接有用的优化。

  • 无指针值与其他值分开存储。

    因此,如果数据结构不严格需要指针,消除数据结构中的指针可能会有利,因为这减少了GC对程序的缓存压力。因此,依赖指针值索引的数据结构,虽然类型不太严格,但可能性能更好。只有在明确对象图复杂且GC花费大量时间进行标记和扫描时才值得这样做。

  • GC将在值中的最后一个指针停止扫描。

    因此,如果明确应用程序在大部分时间内都在进行标记和扫描,将结构类型值中的指针字段分组放在值的开头可能会有利。这只有在明确应用程序在大部分时间内都在进行标记和扫描时才值得这样做。(理论上编译器可以自动完成此操作,但尚未实现,结构字段按照源代码中的编写顺序排列。)

英文:

Here is another discussion about stack heap and GC in A Guide to the Go Garbage Collector

Where Go Values Live

  • stack allocation
    • non-pointer Go values stored in local variables will likely not be managed by the Go GC at all, and Go will instead arrange for memory to be allocated that's tied to the lexical scope in which it's created. In general, this is more efficient than relying on the GC, because the Go compiler is able to predetermine when that memory may be freed and emit machine instructions that clean up. Typically, we refer to allocating memory for Go values this way as "stack allocation," because the space is stored on the goroutine stack.
  • heap allocation
    • Go values whose memory cannot be allocated this way, because the Go compiler cannot determine its lifetime, are said to escape to the heap. "The heap" can be thought of as a catch-all for memory allocation, for when Go values need to be placed somewhere. The act of allocating memory on the heap is typically referred to as "dynamic memory allocation" because both the compiler and the runtime can make very few assumptions as to how this memory is used and when it can be cleaned up. That's where a GC comes in: it's a system that specifically identifies and cleans up dynamic memory allocations.

There are many reasons why a Go value might need to escape to the heap. One reason could be that its size is dynamically determined. Consider for instance the backing array of a slice whose initial size is determined by a variable, rather than a constant. Note that escaping to the heap must also be transitive: if a reference to a Go value is written into another Go value that has already been determined to escape, that value must also escape.


Escape analysis

As for how to access the information from the Go compiler's escape analysis, the simplest way is through a debug flag supported by the Go compiler that describes all optimizations it applied or did not apply to some package in a text format. This includes whether or not values escape. Try the following command, where [package] is some Go package path.

$ go build -gcflags=-m=3 [package]

Implementation-specific optimizations

The Go GC is sensitive to the demographics of live memory, because a complex graph of objects and pointers both limits parallelism and generates more work for the GC. As a result, the GC contains a few optimizations for specific common structures. The most directly useful ones for performance optimization are listed below.

  • Pointer-free values are segregated from other values.

    As a result, it may be advantageous to eliminate pointers from data structures that do not strictly need them, as this reduces the cache pressure the GC exerts on the program. As a result, data structures that rely on indices over pointer values, while less well-typed, may perform better. This is only worth doing if it's clear that the object graph is complex and the GC is spending a lot of time marking and scanning.

  • The GC will stop scanning values at the last pointer in the value.

    As a result, it may be advantageous to group pointer fields in struct-typed values at the beginning of the value. This is only worth doing if it's clear the application spends a lot of its time marking and scanning. (In theory the compiler can do this automatically, but it is not yet implemented, and struct fields are arranged as written in the source code.)

答案6

得分: 0

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

...

return chunk, nil

}

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

...

return &chunk, nil

}

Function1和Function2可能是内联函数。返回的变量不会逃逸。在堆上分配变量是不必要的。

我的示例代码:

package main

type S struct {
x int
}

func main() {
F1()
F2()
F3()
}

func F1() *S {
s := new(S)
return s
}

func F2() *S {
s := S{x: 10}
return &s
}

func F3() S {
s := S{x: 9}
return s
}

根据cmd的输出:

go run -gcflags -m test.go

输出:

command-line-arguments

./test.go:13:6: 可以内联F1
./test.go:18:6: 可以内联F2
./test.go:23:6: 可以内联F3
./test.go:7:6: 可以内联main
./test.go:8:4: 内联调用F1
./test.go:9:4: 内联调用F2
./test.go:10:4: 内联调用F3
/var/folders/nr/lxtqsz6x1x1gfbyp1p0jy4p00000gn/T/go-build333003258/b001/gomod.go:6:6: 可以内联init.0
./test.go:8:4: main new(S)不会逃逸
./test.go:9:4: main &s不会逃逸
./test.go:14:10: new(S)逃逸到堆上
./test.go:20:9: &s逃逸到堆上
./test.go:19:2: 移动到堆上:s

如果编译器足够聪明,F1() F2() *F3()*可能不会被调用。因为这没有意义。

不要关心变量是在堆上还是栈上分配,只需使用它。如果需要,可以通过互斥锁或通道来保护它。

英文:
func Function1() (*MyStructType, error) {
    var chunk *MyStructType = new(HeaderChunk)

    ...

    return chunk, nil
}


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

    ...

    return &amp;chunk, nil
}

Function1 and Function2 may be inline function. And return variable will not escape. It's not necessary to allocate variable on the heap.

My example code:

   package main
   
   type S struct {
           x int
   }
   
   func main() {
           F1()
           F2()
          F3()
  }
  
  func F1() *S {
          s := new(S)
          return s
  }
  
  func F2() *S {
          s := S{x: 10}
          return &amp;s
  }
  
  func F3() S {
          s := S{x: 9}
          return s
  }

According to output of cmd:

go run -gcflags -m test.go

output:

# command-line-arguments
./test.go:13:6: can inline F1
./test.go:18:6: can inline F2
./test.go:23:6: can inline F3
./test.go:7:6: can inline main
./test.go:8:4: inlining call to F1
./test.go:9:4: inlining call to F2
./test.go:10:4: inlining call to F3
/var/folders/nr/lxtqsz6x1x1gfbyp1p0jy4p00000gn/T/go-build333003258/b001/_gomod_.go:6:6: can inline init.0
./test.go:8:4: main new(S) does not escape
./test.go:9:4: main &amp;s does not escape
./test.go:14:10: new(S) escapes to heap
./test.go:20:9: &amp;s escapes to heap
./test.go:19:2: moved to heap: s

If the compiler is smart enough, F1() F2() F3() may not be called. Because it makes no means.

Don't care about whether a variable is allocated on heap or stack, just use it. Protect it by mutex or channel if necessary.

huangapple
  • 本文由 发表于 2012年6月3日 05:46:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/10866195.html
匿名

发表评论

匿名网友

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

确定