Class 30: Pointers in Go
What is a Pointer?
A pointer is a variable that stores the memory address of another variable.
In Go, memory is divided into several segments:
- Code segment: Stores compiled program instructions (functions).
- Data segment: Stores global/static variables and constants.
- Heap: Stores dynamically allocated memory.
- Stack: Stores local variables and function call information.
Pointers help us interact directly with memory addresses.
Symbols to Know:
&
(Ampersand): Used to get the address of a variable.*
(Star/Dereference operator): Used to get the value stored at a memory address.
Example:
x := 20
p := &x // p holds the address of x
*p = 30 // change value at address p (which changes x)
fmt.Println(x) // 30
fmt.Println(p) // address of x
fmt.Println(*p) // 30 (value at address)
Why Use Pointers?
- Efficiency: Instead of copying big structures (like arrays), just pass their memory address.
- Shared Modification: If multiple functions need to modify the same data.
- Memory Management: Especially important in lower-level or high-performance programming.
Without pointers, every function call would copy entire objects. That's sloooow and wasteful!
Pass by Value vs Pass by Reference
Pass by Value:
- A copy of the variable is passed.
- Changes inside the function don't affect the original.
func print(numbers [3]int) {
fmt.Println(numbers)
}
arr := [3]int{1, 2, 3}
print(arr) // Passing a copy
Pass by Reference:
- Pass the address instead of copying.
- Changes inside the function affect the original.
func print2(numbers *[3]int) {
fmt.Println(numbers)
}
arr := [3]int{1, 2, 3}
print2(&arr) // Passing a pointer
Struct Pointers (and why Go is chill with them)
When you have a pointer to a struct, Go is smart enough to let you access fields without needing *
every time.
user1 := User{
Name: "Ruhin",
Age: 21,
Salary: 0,
}
p2 := &user1
fmt.Println(p2.Age) // no need to write (*p2).Age
Go automatically dereferences it for you. Big W.
Full Code Example from Class:
package main
import "fmt"
type User struct {
Name string
Age int
Salary float64
}
func print(numbers [3]int) {
fmt.Println(numbers)
}
func print2(numbers *[3]int) {
fmt.Println(numbers)
}
func main() {
x := 20
p := &x
*p = 30
fmt.Println(x) // 30
fmt.Println("Address:", p)
fmt.Println("Value:", *p)
arr := [3]int{1, 2, 3}
print(arr) // pass by value
print2(&arr) // pass by reference
user1 := User{
Name: "Ruhin",
Age: 21,
Salary: 0,
}
p2 := &user1
fmt.Println(p2.Age)
}
Memory Layout Visualization (CLI-Style)
+--------------------+----------------------------------+
| Segment | What's stored |
+--------------------+----------------------------------+
| Code Segment | main(), print(), print2() |
| Data Segment | (none for local vars here) |
| Stack | arr [3]int {1,2,3}, x=30 |
| | p (pointer to x) |
| | user1 (User struct) |
| | p2 (pointer to user1) |
| Heap | (unused for this simple program) |
+--------------------+----------------------------------+
Detailed Memory Visualization (Addresses and Values)
Stack Memory:
[ Address 0x1000 ] x = 30
[ Address 0x1004 ] p -> 0x1000 (address of x)
[ Address 0x1008 ] arr = [1, 2, 3]
[ Address 0x1010 ] user1 = {"Ruhin", 21, 0.0}
[ Address 0x1018 ] p2 -> 0x1010 (address of user1)
Code Segment:
- Compiled code of main, print, print2
Data Segment:
- Empty (no global variables/constants)
Heap:
- Not used in this example
Extra Example: Swapping Two Numbers with Pointers
Without Pointers (FAIL):
func swap(x, y int) {
temp := x
x = y
y = temp
}
func main() {
a, b := 1, 2
swap(a, b)
fmt.Println(a, b) // still 1 2
}
With Pointers (WIN):
func swap(x, y *int) {
temp := *x
*x = *y
*y = temp
}
func main() {
a, b := 1, 2
swap(&a, &b)
fmt.Println(a, b) // 2 1
}
Quick Summary
&
gets the address.*
gets the value at an address.- Pointers = efficient + powerful.
- Struct pointer fields are auto-dereferenced.
- Pass big things (like arrays, structs) by pointer to save memory.
Bro Tip:
When in doubt, think: "Am I copying a whole dang castle, or just giving a map to it?"
Pointers = the map. ✅