Vee's Golang Notes

Pointers and Storage

21 Sep 2025

Pointers and Storage

Simple Pointers

In Go, a pointer holds the address of a value.

package main
 
import "fmt"
 
func main() {
    i := 5
    p := &i    // p is a pointer to i
    *p = 10    // dereference to modify i
    fmt.Println(*p, i) // prints: 10 10
 
    // Multi-level pointers are allowed but uncommon
    j := &p
    q := &j
    r := &q
 
    // Value of i via r:
    fmt.Println(***r)
}

Type of a pointer depends on what it points to (*int, *string, etc.).


Simple Arrays

Arrays are fixed-size, contiguous collections of elements of the same type.

package main
 
import "fmt"
 
func main() {
    var arr [3]int
    arr[0] = 10
    arr[2] = 20
    fmt.Println(arr) // [10 0 20]
 
    var matrix [3][2]rune
    matrix[0][0] = 'a'
    matrix[2][0] = 'w'
    matrix[2][1] = 'z'
    fmt.Println(string(matrix[2][0]), string(matrix[2][1])) // w z
}

Use slices ([]T) for dynamic length.


Pointers & Arrays

A slice can be indexed or passed around like an array reference.
Pointers can also be used to access elements, but pointer arithmetic is not allowed.

package main
 
import "fmt"
 
func main() {
    name := [5]byte{'h', 'e', 'l', 'l', 'o'}
    p := &name[0]
    fmt.Println(*p) // byte value for 'h'
 
    // Slices are preferred:
    s := []byte("hello")
    fmt.Println(s[0], s[4]) // 104 111
}

Passing Arrays/Slices to Functions

Arrays are copied when passed; slices are references to underlying arrays.

package main
 
import "fmt"
 
func printVector(vec []rune) {
    fmt.Println(string(vec[0]))
}
 
func main() {
    vector := []rune{'a', 'b'}
    printVector(vector)
}

Static vs Automatic vs Dynamic

Go does not expose C++-style storage classes.
All variables are either:

  • Local (automatic): created on the stack or heap as needed.
  • Package-level (static): exist for the program’s lifetime.

Dynamic allocation uses new or make, but Go manages memory with garbage collection.

package main
 
import "fmt"
 
var global int // package-level (static)
 
func main() {
    local := 42 // automatic
    p := new(int)
    *p = 99     // dynamic allocation, but freed by GC
    fmt.Println(global, local, *p)
}

Dynamic Memory

No manual delete. Use slices, maps, or structs; Go’s garbage collector frees memory automatically.


Safe String Building

Go’s string and bytes.Buffer remove the need for raw pointer manipulation.

package main
 
import (
    "fmt"
)
 
func getName() string {
    var first, second string
    fmt.Scan(&first, &second)
    return first + " " + second
}
 
func main() {
    fmt.Println(getName())
}

No raw pointers, no manual allocation, and no risk of leaks. Here’s a Go-centric rewrite of those C++ storage-class and memory-management notes, focusing on how the same ideas map (or don’t map) to Go.


Storage & Lifetime in Go

Go does not have explicit storage classes (static, register, etc.).
Instead, a variable’s lifetime and memory placement depend on:

  • Scope (package, function, block)
  • Escape analysis (whether the value must survive after the current call)

Key categories in Go:

Concept (C++)Go Equivalent
Automatic (stack)Local variables. Created when a function is entered, destroyed when no longer referenced.
Static / globalPackage-level variables. Exist for the entire program.
Dynamic (heap)Values that escape the current function. Allocated on the heap but freed automatically by the garbage collector.

Package-Level (“Static”) Variables

Package-level variables are initialized once and live for the program’s duration.

package main
 
import "fmt"
 
var global int // like C++ static/global
 
func counter() int {
    // function-local static in C++ → closure or package var in Go
    // Here we use a package var to persist state.
    global++
    return global
}
 
func main() {
    fmt.Println("1st call:", counter())
    fmt.Println("2nd call:", counter())
}

Go doesn’t allow a static keyword inside a function,
but you can capture state in a closure or use a package var.


Local (“Automatic”) Variables

Declared inside a function or block.
Created when the block is entered, discarded when unreachable.

func add(x, y int) int {
    sum := x + y // automatic
    return sum
}

The compiler decides whether sum lives on the stack or heap.


Persistent State with Closures

Instead of function-local static:

func makeCounter() func() int {
    count := 0
    return func() int {
        count++
        return count
    }
}
 
func main() {
    next := makeCounter()
    fmt.Println(next()) // 1
    fmt.Println(next()) // 2
}

The captured variable count lives as long as next does.


Dynamic Memory

Go provides garbage-collected heap allocation.
You never call delete—objects are freed when unreachable.

func main() {
    p := new(int)  // allocates on heap if needed
    *p = 42
    fmt.Println(*p)
    // No delete; GC reclaims p automatically
}

new returns a pointer to a zeroed value.
make creates slices, maps, and channels with initialized backing storage.


Arrays, Slices, and Functions

Arrays are fixed length. Slices are the idiomatic way to handle dynamic data.

func printVector(vec []string) {
    fmt.Println(vec[0])
}
 
func main() {
    v := []string{"a", "b"}
    printVector(v) // slice behaves like a pointer + length
}

Passing a slice shares the underlying array (like passing a pointer),
but includes length and capacity—no pointer arithmetic.


Higher-Level Safety

Go encourages higher-level types:

  • string, slice, map, struct, interface
  • sync.Pool, container/list for advanced needs