Back to Resources

Pointers and References

by Dhruv Mehta

Description

This is a simple explanation of pointers and references going into their basics, how they are used and the important differences between them. We also go over important concepts like pass by value, pass by pointer, pass by reference, all of which will be important to understand for Exam 0.

How is data stored in the computer?

To be able to understand how Pointers and References work we first need to understand how data is stored in the computer. Every time we instruct the computer to store some data, the computer finds a memory location and stores the data there. So, any data stored in the computer has 3 essential properties:

  1. Name: The variable name used to store the data.
  2. Value: The data itself i.e. the information to be stored.
  3. Memory Location: The location in memory in the computer where the data has been stored. The location is usually denoted in some sort of hexadecimal format like 0x0010 or 0x0210.

Let us try to look at this with an example: Look at a simple line where we are telling the computer that it should store the value 2 in a variable called a:

int a = 2;

So, the memory block looks like this:

Name Value Memory Location
a 2 0x0010

Note that I am not talking about stack vs heap memory here. For simplicity, I will just be talking about memory in general.

References

The simplest way to think about a reference is as a nickname or an alias. All a reference does is tell the computer that a particular memory block can be referred to by multiple names. The way we denote a Reference is using the & sign. When we see the & on the left-hand side of the = sign of an assignment statement, we know that a reference is being used.

Let us take a look at an example:

int a = 2;
int& x = a;

Here, x is a reference to a which means that data of a can also be referred to by x. This is what the memory block looks like after this code has been executed:

Name Value Memory Location
a or x 2 0x0010

So, creating a reference does not create new memory blocks or modify the data in any way except for just telling the computer that there is now another name with which we can use the memory block.

So, a statement like a = a + 2 will be the exact same as a statement saying x = x + 2 since it is the exact same memory block with just different names. For example, some people call me Dhruv and some people call me Mr. Mehta but it is still me that they are talking about. So the data (in this case, me) is the same in both cases.

Basic Examples using References:

Example 1

#include <iostream>

int main() {
    int number = 5;
    int& ref = number;
    ref = 10; // Modifying 'number' through the reference 'ref'
    std::cout << "Number: " << number << std::endl;
    return 0;
}

Output:

Number: 10

Explanation: In this example, a variable is being created called “number” with the value 5. So, a memory block is created with the name “number” and value as 5. In the next line, we are creating a reference to called ref. So, we are basically telling the computer that “ref” is another name that we can use for the same memory block. So, when we use “ref” or “number” we are referring to the same memory block. So any changes to ref change the value of the memory block.

Example 2:

#include <iostream>

int main() {
    int number = 5;
    int& ref = number;
    int x = number;
    ref = 10; // Modifying 'number' through the reference 'ref'
    std::cout << "Value in number: " << number << std::endl;
    std::cout << "Value in x:" << x << std::endl;
    return 0;
}

Output:

Value in number: 10
Value in x: 5

Explanation: Here, ref and number are referring to the same memory block. But, when we assign x, since x is not a reference, x is not a name for the same memory block but it creates a copy of the original one, so any changes in the original one will not reflect in x since it is a completely different memory block with its own value, location, and name.

Pointers

A pointer is a variable that stores the address or location of a memory block. So, when we create a pointer another memory block is created which contains, as its data, the memory location of another block.

Let us look at these statements:

int a = 2;
int* x = &a;

(Note that here the & is the “Address of” operator and not a Reference. The two are completely different and independent operations.)

So, here the memory is stored in the following way:

Name Value Memory Location
a 2 0x0010
x 0x0010 0x2180

So, the value (data stored) in memory block x is the memory location of a.

To simplify this even further, instead of using hexadecimal codes to denote memory locations, I am going to be using actual physical addresses.

Let us consider the following code:

int a = 2;
int* x = &a;
Name Value Memory Location
a 2 Siebel Center for Computer Science
x Siebel Center for Computer Science Illini Union

So, this means that x is sitting at the Illini Union. However, x knows that it can find a at Siebel Center for Computer Science. So, since x knows the location of a, we can use x to get to a.

That way we can get to a using x is using the * operator (Dereferencing). If we write some code like:

*x = *x + 3;

Difference Between Reference and AddressOf Operators

I would highly recommend reading this section carefully because a lot of people get confused between the two and make many mistakes as a result. Before we go into this explanation, I just want to say that this is one of the things I hate most about C++. Why did they have to use the & sign for both these operations when these operations do completely unrelated things?

The main difference between the usage of the & in the Reference and AddressOf operations is that:

Again, the two are completely different and do very different things.

Let’s just recap with the earlier examples we were using:

int a = 2;

This line of code creates a memory block like this:

Name Value Memory Location
a 2 0x0010
int& r = a;
int * p = &a;

After these two lines of code, the memory blocks look like this:

Name Value Memory Location
a or r 2 0x0010
p 0x0010 0x2180

Reference to Pointers

Okay, I know you are getting angry at me with how weird stuff

is getting but I guarantee this is as weird as it gets and once you read the explanation you’ll realize that this is not as bad as it sounds. Again, let’s look at some code and the corresponding memory blocks:

int a = 2;
int* p = &a;
Name Value Memory Location
a 2 0x0010
p 0x0010 0x2180

Now understand that p is the name of the memory block and so, we can also give the memory block a nickname other than p. That is all a reference to a pointer is. It is giving a nickname to a pointer variable.

It is written this way:

int* & pref = p;

This is implying that pref is a nickname for the memory block of p and the data type of memory block is an int*.

The resultant memory blocks are:

Name Value Memory Location
a 2 0x0010
p or pref 0x0010 0x2180

Basic Examples using Pointers

Example 1

#include <iostream>

int main() {
    int number = 5;
    int* ptr = &number; // Declare a pointer and initialize it with the address of 'number'
    std::cout << "Value of number: " << *ptr << std::endl;
    std::cout << "Address of number: " << &number << std::endl;
    std::cout << "Value stored in ptr: " << ptr << std::endl;
    *ptr = 10;
    std::cout << "Updated value of number: " << *ptr << std::endl;
    return 0;
}

Output:

Value of number: 5
Address of number: 0x7fffe4c1aabc
Value stored in ptr: 0x7fffe4c1aabc
Updated value of number: 10

Explanation: Here we have a memory block with the name “number” which has the value 5 and it is stored at the memory address 0x7fffe4c1aabc. We then create a pointer. This pointer is just another memory block with the name “ptr” which has the value 0x7fffe4c1aabc and some other memory location. We then use the * operator to use ptr to access the memory block of “number” and so 5 gets printed out. Then we are printing out the memory location of the “number” memory block using the & which is the AddressOf operator. In the next line, we are printing out what ptr itself stores, so here we see that ptr stores the memory location of “number” and so 0x7fffe4c1aabc is printed out. We also see that we can update the value of a memory block using a pointer to it. So, here we do *ptr to access the memory block and then assign a new value to it so it gets updated.

Example 2

#include <iostream>

int main() {
    int number = 5;
    int* ptr1 = &number; // Declare a pointer and initialize it with the address of 'number'
    int* ptr2 = &number;
    std::cout << "Value of block that ptr1 points to: " << *ptr1 << std::endl;
    std::cout << "Value of block that ptr2 points to: " << *ptr2 << std::endl;
    std::cout << "Value stored in ptr1: " << ptr1 << std::endl;
    std::cout << "Value stored in ptr2: " << ptr2 << std::endl;
    *ptr1 = 10;
    std::cout << "Made an Update" << std::endl;
    std::cout << "Updated Value of block that ptr1 points to: " << *ptr1 << std::endl;
    std::cout << "Updated Value of block that ptr2 points to: " << *ptr2 << std::endl;
    std::cout << "Value stored in ptr1: " << ptr1 << std::endl;
    std::cout << "Value stored in ptr2: " << ptr2 << std::endl;
    return 0;
}

Output:

Value of block that ptr1 points to: 5
Value of block that ptr2 points to: 5
Value stored in ptr1: 0x7FFFEA350A8B
Value stored in ptr2: 0x7FFFEA350A8B
Made an Update
Updated Value of block that ptr1 points to: 10
Updated Value of block that ptr2 points to: 10
Value stored in ptr1: 0x7FFFEA350A8B
Value stored in ptr2: 0x7FFFEA350A8B

Explanation: This is very similar to the previous example. Here, we create a memory block called number and assign it a value of 5. We then create 2 points ptr1 and ptr2 which point to the memory block of number. You can see that both the pointers have the same value since they are storing the location of the same memory block. Then we update the value of the memory block using ptr1. We can then see using the print statements that the value of the memory block has been updated. We can see that both the pointers are still pointing to the same block. *ptr2 will print the updated value since ptr2 points to the same number block which has been updated.

Example 3

#include <iostream>

int main() {
    int num1 = 5;
    int num2 = 20;
    int* ptr = &num1;
    std::cout << "Value stored in num1: " << num1 << std::endl;
    std::cout << "Value stored in num2: " << num2 << std::endl;
    std::cout << "Value stored in ptr: " << ptr << std::endl;
    ptr = &num2;
    *ptr = 50;
    std::cout << "Updated" << std::endl;
    std::cout << "Value of num1: " << num1 << std::endl;
    std::cout << "Value of num2: " << num2 << std::endl;
    std::cout << "Value stored in ptr: " << ptr << std::endl;
    return 0;
}

Output:

Value stored in num1: 5
Value stored in num2: 20
Updated
Value stored in num1: 5
Value stored in num2: 50

Explanation: Here, we are creating 2 memory blocks num1 and num2 which have the values 5 and 20 respectively. Then we create a pointer ptr which points to the memory block num1. We can see this in the first 3 lines of the output. Then, with the line ptr = &num2, we are changing the value stored in ptr and redirecting it to num2. Now, ptr stores the location of the memory block of num2. So, when we dereference ptr, we are given the num2 block. So, now, *ptr2 = 50 changes the value stored in the num2 block. We can see in the output that the value of num2 has been changed and ptr now stores the location of num2.

More Complex Examples using Pointers and References

Example 1

#include <iostream>

void modifyValue(int& ref) {
    ref = 50; // Modify the value using the reference
}

int main() {
    int num = 20;
    std::cout << "Value of num: " << num << std::endl;
    modifyValue(num); // Modify the value of 'num' using the reference
    std::cout << "New value of num: " << num << std::endl;
    return 0;
}

Output:

Value of num: 20
New Value of num: 50

Explanation: Here, we create a memory block called num having a value of 20. We then pass num to the modifyValue. As you can see from the & sign, the parameters of modifyValue are references. What this essentially does is:

int& ref = num;

So, ref is a reference to num which means that it is just another name of the same memory block as num. So, any changes made to ref in modifyValue change the memory block that num and ref refer to. So, when we print num, it now shows the updated value. This is known as “pass by reference” since the parameters are references to the variables passed to the functions.

Example 2

#include <iostream>

void modifyValue(int* p) {
    *p = 50; // Modify the value using the reference
}

int main () {
    int num = 10;
    std::

cout << "Old Value of Num: " << num << std::endl;
    modifyValue(&num);
    std::cout << "New Value of Num: " << num << std::endl;
}

Output:

Old Value of Num: 10
New Value of Num: 50

Explanation: Here, we create a memory block called num having a value of 2. Then we pass the address of this memory block to the function modifyValue which creates a pointer p storing the location of the memory block num. We then dereference p to get access to the memory block of num and then we change the value of that which means that num memory block is now containing the new value 50. This is known as ‘pass by pointer’ when we pass the address of our memory block to a function.

Example 3

#include <iostream>

void modifyValue(int x) {
    x = 50; // Modify the value using the reference
}

int main () {
    int num = 10;
    std::cout << "Old Value of Num: " << num << std::endl;
    modifyValue(num);
    std::cout << "New Value of Num: " << num << std::endl;
}

Output:

Old Value of Num: 10
New Value of Num: 10

Explanation: When we do not use pointers or references while passing a variable to a function, then what the function does is creates a duplicate/copy of the memory block which was passed to it. This new memory block contains the same data/value but it is separate from the old memory block. So, any changes made to the data in this new memory block do not affect the old memory block. This is known as ‘pass by value’.

Example 4

#include <iostream>

int main() {
    int num = 5;
    int* ptr1 = &num; // Pointer to an integer
    int** ptr2 = &ptr1; // Pointer to a pointer to an integer
    std::cout << "Value of num: " << num << std::endl;
    std::cout << "Deref of ptr1: " << *ptr1 << std::endl; // Dereference ptr1
    std::cout << "Double Deref of ptr2: " << **ptr2 << std::endl; // Dereference ptr2 twice
    return 0;
}

Output:

Value of num: 5
Deref of ptr1: 5
Double Deref of ptr2: 5

Explanation: Here, we first create a memory block with the name num and the value of 5. Then we create a pointer called ptr1 which points to the memory block. Then we create another pointer ptr2 which is pointing to the memory block containing ptr1. So, we have ptr2 pointing to ptr1 which is pointing to num. When we dereference ptr2 as (*ptr2) we get the value stored in it i.e. ptr1, and when we dereference this, we get the num block. So, a **ptr2 is a double dereference which gets us from ptr2 to num.

Example 5

#include <iostream>

int main () {
    int num1 = 3;
    int num2 = 5;
    int* p = &num1;
    int* & pref = p;
    *p = 20;
    pref = &num2;
    *p = 10;
    std::cout << "Num1: " << num1 << std::endl;
    std::cout << "Num2: " << num2 << std::endl;
}

Output:

Num1: 20
Num2: 10

Explanation: Here, we first create 2 blocks of memory. First a block with the name num1 and value 3 and second block with a name num2 and value 5. Then we create another memory block by the name p, which is a pointer which means that it stores the address of num1. So, we can use the block p to access num1. Now, we create a reference “pref” of the block p. So, now the block which had the name p has another name pref (note that p and pref are names of the same block of memory). Now we dereference p. So, we get access to the block that it is pointing to which is num1 and we change the value of num1 to 20. Now we change pref (which is the same block as p) to point to num2 instead of num1. So, now the block (which has the names p and pref) points to num2. Now, we dereference pref and get access to the num2 block and change the value to 10. So, num1 has a value 20 and num2 has a value 10.

Example 6

#include <iostream>

int main() {
    int num1 = 3;
    int num2 = 5;
    int* p = &num1;
    int* & pref = p;
    pref = &num2;
    *p = 10;
    std::cout << "Num1: " << num1 << std::endl;
    std::cout << "Num2: " << num2 << std::endl;
}

Output:

Num1: 3
Num2: 10

Explanation: This is a lot like the previous example with a small change to further clarify how references and pointers work. Here, we first create 2 blocks of memory. First a block with the name num1 and value 3 and second block with a name num2 and value 5. Then we create another memory block by the name p, which is a pointer which means that it stores the address of num1. So, we can use the block p to access num1. Now, we create a reference “pref” of the block p. So, now the block which had the name p has another name pref (note that p and pref are names of the same block of memory). Now we say that the block of pref will store the address of num2. So, earlier this block was storing the address of num1 and now it will instead store the address of num2. So, to be clear, the block (which has the name p and pref) stores the address of num2 now. Then we dereference p which means we get access to the block that it is pointing to which is num2 and then we change the value of num2 to 10. So, num1 remains unchanged and num2 is now 10.