Search code examples
memorylayoutgo

Go memory layout compared to C++/C


In Go, it seems there are no constructors, but it is suggested that you allocate an object of a struct type using a function, usually named by "New" + TypeName, for example

func NewRect(x,y, width, height float) *Rect {
     return &Rect(x,y,width, height)
}

However, I am not sure about the memory layout of Go. In C/C++, this kind of code means you return a pointer, which point to a temporary object because the variable is allocated on the stack, and the variable may be some trash after the function return. In Go, do I have to worry such kind of thing? Because It seems no standard shows that what kind of data will be allocated on the stack vs what kind of data will be allocated on the heap.

As in Java, there seems to have a specific point out that the basic type such as int, float will be allocated on the stack, other object derived from the object will be allocated on the heap. In Go, is there a specific talk about this?


Solution

  • The Composite Literal section mentions:

    Taking the address of a composite literal (§Address operators) generates a unique pointer to an instance of the literal's value.

    That means the pointer returned by the New function will be a valid one (allocated on the stack).
    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.

    You can see more in this answer and this thread.

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

    It's worth noting that the words "stack" and "heap" do not appear anywhere in the language spec.


    The blog post "Escape Analysis in Go" details what happens, mentioning the FAQ:

    When possible, the Go compilers will allocate variables that are local to a function in that function's stack frame.
    However, 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.
    Also, if a local variable is very large, it might make more sense to store it on the heap rather than the stack.

    The blog post adds:

    The code that does the “escape analysis” lives in src/cmd/gc/esc.c.
    Conceptually, it tries to determine if a local variable escapes the current scope; the only two cases where this happens are when a variable’s address is returned, and when its address is assigned to a variable in an outer scope.
    If a variable escapes, it has to be allocated on the heap; otherwise, it’s safe to put it on the stack.

    Interestingly, this applies to new(T) allocations as well.
    If they don’t escape, they’ll end up being allocated on the stack. Here’s an example to clarify matters:

    var intPointerGlobal *int = nil
    
    func Foo() *int {
        anInt0 := 0
        anInt1 := new(int)
    
        anInt2 := 42
        intPointerGlobal = &anInt2
    
        anInt3 := 5
    
        return &anInt3
    }
    

    Above, anInt0 and anInt1 do not escape, so they are allocated on the stack;
    anInt2 and anInt3 escape, and are allocated on the heap.


    See also "Five things that make Go fast":

    Unlike C, which forces you to choose if a value will be stored on the heap, via malloc, or on the stack, by declaring it inside the scope of the function, Go implements an optimisation called escape analysis.

    Go’s optimisations are always enabled by default.
    You can see the compiler’s escape analysis and inlining decisions with the -gcflags=-m switch.

    Because escape analysis is performed at compile time, not run time, stack allocation will always be faster than heap allocation, no matter how efficient your garbage collector is.