Heap, stack, and pointer

preface

Heap and stack have always been hot topics in the field of computer. In the final analysis, they have nothing to do with programming language, but are both memory partition at the level of operating system. I will try to briefly separate these concepts and talk about my understanding of them.

The stack

Each value in each function is exclusive to the stack and cannot be accessed from other stacks. Each function frame has its own private stack, whose life cycle is born and dies at the beginning and end of the method, and is released at the end of the method. Compared with heap, stack has the advantage of being lightweight and can be used and discarded at any time, and its lifetime follows the function.

The heap

In layman’s terms, if the stack is a private house of functions, the heap is a large people’s square that can be shared. The heap acts as a global access block, and its space is managed by GC(demolition team).

The heap is not self cleaning like stacks, so there is a bigger cost to using this memory. Primarily, the costs are associated with the garbage collector (GC), which must get involved to keep this area clean.

Unlike the stack, which is freed at the end of a function call, the heap is not freed automatically.

Garbage Collection (GC)

Garbage collection. Garbage collection has several strategies. In general, every variable that exists in the heap but is no longer referenced by a pointer is collected. “These allocations put pressure on the GC because every value on the heap that is no longer referenced by a pointer, Because garbage collection involves memory operations, there are often many factors to consider, but fortunately, over time, some programming languages have developed garbage collection strategies that are powerful enough (Java, Go) that we don’t need to interfere with memory cleaning most of the time, leaving it to the underlying scheduler.

The downside of allocating heap memory is that it adds stress to the next GC, but the benefit is that it can be shared by other stacks, and compilers tend to store it in the heap for the following scenarios:

  • Try to request a larger structure/array
  • Variables will be used for a certain amount of time
  • Cannot confirm size of variable request during compilation

Pointer to the

A pointer is essentially the same as any other type, except that its value is a memory address (reference), which I understand to be the house number of a memory block. There is an oft-repeated saying:

When to use a pointer depends on when to share it.

Pointers serve one purpose, to share a value with a function so the function can read and write to that value even though the value does not exist directly inside its own frame.

Pointers are used to share variables between different function method blocks (stack intervals) and to provide variable reads and writes.


With that in mind, Pointers refer to memory addresses, heaps are shared modules, and Pointers are meant to share the same memory slice. In Go, all parameters are passed by value, and Pointers are passed by the value of Pointers.

Program instance pointer sharing

The main function

func main(a) {
	var v int = 1
	fmt.Printf("# Main frame: Value of v:\t\t %v, address: %p\n", v, &v)
	PassValue(v, &v)
	fmt.Printf("# Main frame: Value of v:\t\t %v, address: %p\n", v, &v)
}
Copy the code

A subroutine

func PassValue(fv int, addV *int) {
	// The address of fv belongs only to the function and is allocated by the function stack
	fmt.Printf("# Func frame: Value of fv:\t\t %v, address: %p\n", fv, &fv)
	// This change only applies to this function
	fv = 0
	fmt.Printf("# Func frame: Value of fv:\t\t %v, address: %p\n", fv, &fv)

	/* * The pointer is visible from the global address passed in by the main function, * because the pointer changes the contents of the same memory block */
	*addV++
	fmt.Printf("# Func frame: Value of addV:\t %v, address: %p\n", *addV, addV)
}
Copy the code

Output:

# Main frame: Value of v:		 1, address: 0xc000054080
# Func frame: Value of fv:		 1, address: 0xc0000540a0
# Func frame: Value of fv:		 0, address: 0xc0000540a0
# Func frame: Value of addV:	 2, address: 0xc000054080
# Main frame: Value of v:		 2, address: 0xc000054080
Copy the code

You can see that the pass pointer operates on the same address in the subfunction (0xC000054080), so the change to v at the exit of the subfunction is visible in the main function. The address of the fV passed to the subfunction is already in the subfunction’s stack, which will be released when the function ends.


Stack the escape

  1. Accessing the external stack

The value of a variable at the end of a function’s execution does not end with the stack, and escapes to the heap (usually as a global pointer), which can be shared externally and can be analyzed using go’s built-in tools. Here is a chestnut that escapes the stack.

Go //go:noinline func CreatePointer() *int {return new(int)} ' '** analysis **' 'go $go build-gcFlags "-m-m-l" escape.go # command-line-arguments .\escape.go:9:12: new(int) escapes to heap .\escape.go:9:12: From ~r0 (return) at.\ escape. Go :9:2 return new(int) returns the new pointer to the caller, This pointer ends with an external reference to the pointer that is out of the scope of the function stack, so the compiler prompts that it will be allocated to the heap.Copy the code
  1. Compilation not determined

There is another scenario in which stack escape can occur. Here’s another one:

```go func SpecifySizeAllocate() { buf := make([]byte, 5) println(buf) } func UnSpecifySizeAllocate(size int) { buf := make([]byte, Size) println(buf)} ' '** analysis **' 'go $go build-gcflags "-m -m" escape. Go # command-line-arguments. can inline SpecifySizeAllocate as: func() { buf := make([]byte, 5); println(buf) } .\escape.go:10:6: can inline UnSpecifySizeAllocate as: func(int) { buf := make([]byte, size); println(buf) } .\escape.go:6:13: SpecifySizeAllocate make([]byte, 5) does not escape .\escape.go:11:13: make([]byte, size) escapes to heap .\escape.go:11:13: From make([]byte, size) (non-constant size) at.\escape. Go :11:13" For specifysizeallocate (), the function generates stack escapes. Why? The ** compiler does not know the value of size at compile time, so it cannot allocate an interval of the exact size to buF in the stack. If the compiler sees such an undefined size allocation, it will allocate it to the heap. This also explains why the SpecifySizeAllocate() function does not generate escapes.Copy the code

Postscript:

Elapsed time to the evolution of the compiler has been smart enough to apply for distribution of stack can be at ease to them to do it, most business code does not need too much to consider the distribution of the variables, here is just a try at the variables in memory program, understand some of the concepts, just want to know the stack analysis performance tuning one way.

Of course, this is not just a performance issue. In parameter passing, it is best to decide whether to use a value copy or a pointer based on the variable’s future scope.

Go bring some tools convenient implementation, we analyze the bottom in the follow the advice of his predecessors, the stack may be more suitable for the analysis in the business code optimization phase is completed, the early in order to ensure the functionality and readability of the code, ape should first focus on the implementation procedure, when the performance bottleneck, try from the stack FenPeiChu optimization may be to consider, After all, there’s a rule called don’t optimize too early.

Reference links:

– How do I know whether a variable is allocated on the heap or the stack golang.org/doc/faq#sta… Ardan quadruple dry LABS (recommended) : www.ardanlabs.com/blog/2017/0… Go: Should I Use a Pointer instead of a Copy of my Struct? Medium.com/a-journey-w… Memory: Stack vs Heap www.gribblelab.org/CBootCamp/7…