Pointers in Go: Understanding Memory References

Edwin Siby
5 min readAug 4, 2023

--

In Go, pointers are powerful and essential features that allow developers to work with memory addresses directly and achieve efficient data manipulation. Pointers provide a way to pass references to data instead of copying the entire data, which can be especially beneficial for large data structures. Understanding how pointers work in Go is crucial for writing efficient and performant code.

* (Asterisk):

  • It is used to declare a pointer variable. For example, *int represents a pointer to an integer, *string represents a pointer to a string, and so on.
  • It is also used to dereference a pointer, which means accessing the value stored at the memory address pointed by the pointer variable.

& (Ampersand):

  • It is used to get the memory address of a variable. For example, &num gives the memory address of the variable num.
  • It is commonly used when working with pointers, passing variables’ memory addresses to functions, or initializing pointers with existing variables.
Image from geeksforgeeks

Example of declaring a pointer and dereferencing it

var num int = 42
var ptr *int // Declaring a pointer to an integer
ptr = &num // Assigning the memory address of 'num' to 'ptr'
fmt.Println(*ptr) // Dereferencing 'ptr' to get the value (prints 42)

Let us dive into more examples

In Go, pointers are denoted by the * symbol before the type. For example, *int represents a pointer to an integer. To access the value stored in the memory address pointed to by a pointer, you use the * symbol before the pointer variable.

Here’s a simple example of pointers in Go

package main

import "fmt"

func main() {
// Declare an integer variable
var num int = 42

// Declare a pointer to an integer and assign the memory address of 'num' to it
var ptr *int = &num

// Print the value of 'num' and the memory address stored in 'ptr'
fmt.Printf("Value of 'num': %d\n", num)
fmt.Printf("Memory address of 'num': %p\n", &num)

// Print the value of the memory address pointed by 'ptr'
fmt.Printf("Value pointed by 'ptr': %d\n", *ptr)

// Change the value of the memory address pointed by 'ptr'
*ptr = 100

// The value of 'num' is now changed because 'ptr' points to 'num'
fmt.Printf("New value of 'num': %d\n", num)
}

Explanation:

  1. We declare an integer variable num and assign the value 42 to it.
  2. We declare a pointer to an integer ptr and assign the memory address of num to it using the & symbol.
  3. We print the value of num, the memory address of num, and the value pointed to by the ptr (using the * symbol).
  4. We then change the value of the memory address pointed by ptr to 100, which also changes the value of num since they share the same memory address.

When you run the above code, you’ll see the output like this

Value of 'num': 42
Memory address of 'num': 0xc0000... (some hexadecimal address)
Value pointed by 'ptr': 42
New value of 'num': 100

In Go, you can use pointers with structs to pass references to struct instances instead of copying the entire struct. This can be particularly useful when you have large structs that you want to manipulate efficiently without incurring the cost of copying them around.

Here’s an example of using pointers with structs in Go:goCopy code

package main
import "fmt"

// Define a simple struct representing a person
type Person struct {
Name string
Age int
IsMale bool
}
// A function that takes a pointer to a Person struct and modifies it
func modifyPerson(p *Person, newName string, newAge int) {
p.Name = newName
p.Age = newAge
}
func main() {
// Create a Person struct instance using a pointer
personPtr := &Person{Name: "John", Age: 30, IsMale: true}

// Print the initial values of the person
fmt.Printf("Initial Name: %s, Age: %d\n", personPtr.Name, personPtr.Age)

// Modify the person using the function
modifyPerson(personPtr, "Jane", 25)

// Print the updated values of the person
fmt.Printf("Updated Name: %s, Age: %d\n", personPtr.Name, personPtr.Age)
}

Explanation:

  1. We define a simple Person struct with three fields: Name, Age, and IsMale.
  2. We declare a function modifyPerson that takes a pointer to a Person struct as its first argument. This function modifies the Name and Age fields of the person.
  3. In the main function, we create a pointer to a Person struct named personPtr and initialize it with values.
  4. We print the initial values of the person using the fmt.Printf function.
  5. We call the modifyPerson function, passing the pointer to the personPtr and new values for the Name and Age.
  6. The modifyPerson function updates the Name and Age fields of the person pointed to by personPtr.
  7. We print the updated values of the person to show that the modifications have taken effect.

When you run the code, you’ll see the output like this

Initial Name: John, Age: 30
Updated Name: Jane, Age: 25

As you can see, using a pointer to a struct allows us to modify the struct instance directly, and the changes are reflected outside the function where the modifications were made. This avoids the need to create a new copy of the entire struct, making the code more efficient and memory-friendly, especially for large structs.

“Don’t just read the article; get your hands dirty by experimenting with these code examples.”

These examples demonstrate how pointers allow us to access and modify the underlying data directly by using the memory address instead of the actual value. Pointers are especially useful when dealing with large data structures like arrays, slices, and complex objects, as they prevent unnecessary data copying and improve performance.

Remember to handle pointers with care, as improper use of pointers can lead to bugs like null pointer dereferences and memory leaks. Go makes it relatively safe to use pointers compared to some other languages by providing garbage collection, but it still requires attention to memory management.

--

--