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 / global | Package-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