For some of us who didn’t start programming with a low high-level language (like C), the idea of “pointers” is alien. It took me a while to fully grasp what “pointers” were when I started learning Go. I understood the concept with Golang first then went back to C and C++ and realised it’s the same idea.
Here’s my attempt to demystify “pointers”. But before that, let’s talk about addresses.
Addresses: Living Spaces for Code and Data
An address is a reference to a specific memory location. Code and variables are loaded in memory for a processor to work with. For a simple computation like a + b
(assuming a
and b
are integers or whatever operand +
can operate on in a given language) to be possible. The processor has to know where precisely in memory they are stored so it can pick them and then ADD them; the processor has to know the addresses of these variables.
Pointer: The Neighbourhood Savant
So what is a pointer? Figuratively, a pointer is like the neighbourhood greybeard. He’s the guy that knows everyone in the neighbourhood, where they live, what they do, etc. He’s the guy to go to if you want to know the address of variable a
.
In Golang, a pointer holds the memory address of a value. That’s all it does.
Here’s an example:
package main
import "fmt"
func main() {
var number int = 33
var addressOfNumber *int = &number // Pointer to the `number` variable
fmt.Print(addressOfNumber)
}
// Prints: 0xc0000b4008
In Go, the &
operator generates a pointer to its operand so that &number
becomes a pointer to the variable number
. &number
knows that number
lives at 0xc0000b4008
.
var addressOfNumber *int = &number
is how to declare a pointer variable. An implicit declaration would look like this: addressOfNumber := &number
.
OK. So now we know that pointers hold the addresses of values in memory.
Awesome!
So What’s The Point?
To illustrate the point of pointers, let’s assume you are working somewhere with a wage of $1,500. A new boss comes and asks your junior developer to write a program to double everyone’s payment! It’s all excitement for everyone; you’re elated!
But your developer comes in with this and asks for a code review:
package main
import "fmt"
func doubleWage(wage int) {
wage *= 2
}
func main() {
var wage int = 1500 // Salary in USD
doubleWage(wage)
fmt.Println(wage)
}
Looking at this, what do you think your new wage will be? $1,500 or $3,000? At a glance, this looks like it’s going to double your wage to $3,000, but it doesn’t! And that’s where pointers come in.
Why We Didn’t Get Our Pay Raise
Our wage from the previous program stayed that same, $1,500. This is because Golang is a pass-by-value language. Pass-by-value is an evaluation strategy, and according to Wikipedia:
In pass by value, the argument expression is evaluated, and the resulting value is bound to the corresponding variable in the function (frequently by copying the value into a new memory region). If the function or procedure can assign values to its parameters, only its local variable is assigned. That is, anything passed into a function call is unchanged in the caller’s scope when the function returns.
So this is what’s happening in our program above:
func main() {
var wage int = 1500 // Original value of `wage`
doubleWage(wage) // COPY `wage`
fmt.Println(wage) // Print original value of `wage`
}
doubleWage(wage)
does its job of double the wage but we do nothing with that doubled value!
The Fight for A Raise
There’s a particular operator we haven’t mentioned… *
. When we use it with a pointer, instead of getting an address of some value, we get a value itself.
From this:
var number int = 33
var addressOfNumber *int = &number // Pointer to the `number` variable
We know that addressOfNumber
is a pointer (returns the address of number
), if we did *addressOfNumber
we’d get 33
, the value itself. We can go ahead and do something like *addressOfNumber = 55
, and this will set the value of number
to 55; we call this “dereferencing”.
With the newfound knowledge here’s how we can fix our original program to get the pay raise we deserve:
package main
import "fmt"
func doubleWage(wage *int) {
*wage *= 2
}
func main() {
var wage int = 1500 // Salary in USD
doubleWage(&wage)
fmt.Println(wage)
}
// Prints 3000!
We’ve made our doubleWage
accept a pointer as a parameter and modified whatever the pointer points to with *wage *= 2
. Now to use it, since doubleWage
points to an address we have to pass an address to it with &wage
, and that’s all it takes.
Pointers allow us to jump scope, and this, ladies and gentlemen, is the point of pointers.