Pointers and references

One of the main aspects we need to be concerned about when doing any kind of programming are the resources. There are two main types of resources:
  • Spatial – how much memory your program uses
  • Temporal – how long your program runs
In this lecture we will discuss one of the most fundamental concepts implemented in C++ for memory management – pointers and references.

Memory and pointers

Until now, the concept of memory was only used when we talked about variable declaration and initialization. We will now introduce some new terminology and ways of thinking about other means of accessing the memory. The main tool for accessing the memory in a different way is through variables called pointers.
 
Pointers are variables which, instead of storing a value of a certain data type, store the address of a certain variable.
picture

In the figure above, we see that the value 3 is stored in the memory location at the address 0x1234. Until now, the only way we could access this value is if there was a variable declared at this address. Now, we have an additional way and that is through a pointer (pointer p in the figure). Let’s see how this works in pseudocode:

int *p = 0x1234; //p points to the memory address 0x1234
cout << p; //prints out the memory address 0x1234 
cout << *p; //prints out the value (3) stored in the memory location 0x1234
The key points you should take from this example:
  • Pointers are declared by specifying the data type it points to, followed by the character *, and finally the pointer’s name (the naming convention is the same as for regular variables)
  • After declaring the pointer, if you write just the name of the pointer, you are accessing the address it’s pointing to
  • After declaring the pointer, if you write * followed by the pointer’s name, you are accessing the memory location on the address stored in the pointer
Depending on the variable they are pointing to (storing their address), pointers, like variables, can be declared with different data types. The difference is that the data type of the pointer only determines the location it can point to ( for example: a pointer to an int cannot point to a double).
 
Another way to use pointers is by pointing to a specific variable.
picture

Hence variables are essentially memory locations, they must be stored on some address. The trick now is how do we get the variable’s address? We can use an ampersand operator, denoted as &. We use it by writing the operator followed by the name of the variable and the result is the address where that variable is stored. The following code shows how we point to variables:

int x = 1;
int *a = &x;
cout << a; //prints the address where x is stored
cout << *a; //prints the value stored in x 

Hence pointers work on the most basic level in the same way as primitive data types (they store some value, in this case: an address), they are stored in memory the same way as primitive data types. We will now look at the complete model of the memory, including both primitive data type variables and pointers.

picture

In the figure above, we have a variable named x initialized with the value 3 and a pointer p pointing to the variable x. In pseudocode, this would look like this:

int x = 3;
int * p = &x;

cout << x; //prints out 3
cout << *p; //prints out 3
cout << p; //prints out 0x1234
cout << &x; //prints out 0x1234
cout << &p; //prints out 0x1237

Using pointers for array indexing

When we learned about arrays, we said that they are essentially a sequence of variables with the same data type. When we wanted to grab or set a specific value inside of the array, we used indexing – typing the square brackets next to the array and putting the index inside the brackets. In this lecture we have also learned about pointers and how they are used to store the address of the place in memory where the variable is stored. What would happen if we tried to initialize a pointer to point to an array? What would the value be when doing “* <name_of_the_pointer>“?

nizpok

In the figure above, we see a small portion of the memory. In the memory spaces from address 0x1234 till 0x1236, an array is stored with values 1 till 3. We see a pointer pointing at the start of the array, specifically, at the first element. The last sentence is exactly what happens when we assign the address of the array to a pointer. Here is the equivalent pseudocode for the above figure:

int a[] = {1, 2, 3}; //Adress of the array is starting from 0x1234
int * p = a;
There is something missing here, or is it? When using variables as places where the pointer points to we had to extract their addresses using the & operator. Here we are just using the array’s name instead. The reason is that the array “a is also a pointer! So in some sense, you have been using pointers since the first lesson! The only thing that “masks” the array from looking like a pointer is the square brackets used for indexing the array, which actually do a bit more complex task than it may have seemed to you. Let’s look at how they work from a pointer perspective:
  • When using the square brackets to index the array, the address of the first element is taken and the number inside the square brackets
  • Those two numbers are added up and the resulting address is used as an access point to the memory location
So, if pointers are the same as arrays, can we use them instead of the original arrays? The answer is yes and the next set of pseudocodes gives you a possible use:
int a[] = {1, 2, 4, 8};
int * p = a;
p[0] = 3; // Same as a[0] = 3;

Now comes the big question: Can arrays be used as pointers? The answer is yes, but with a slight more work. Elements in arrays are stored in sequential memory locations. If a pointer stores the address to the first element, for us to get the next one, we would have to increase the address stored inside the pointer by one and then use the * to access the element. What about accessing the 3rd element? Increase the pointer’s address by two and access the element from that address! It seems that we can access the Nth element of an array by adding N – 1 to the pointer and accessing the element. Here’s the code for this part:

int a[] = {1, 2, 3, 5, 8, 13};
cout << *(a + 0); //prints out 1
cout << *(3 + a); //prints out 5
int * p = a + 4; //p points to the 5th element (8)
p[1] -= 1; //The last element of the array is now 12 

References

The last thing left for this lecture are references. Any time we declared a variable, we needed to supply an unique identifier (in this case the name of the variable) and that identifier was essentially used to access a specific memory location given to us. We also saw that if we get the memory location’s address, we could create another connection to the exact same memory location with the help of pointers. Now, imagine that we didn’t want to use pointer-like logic for accessing a specific memory location, but instead wanted the same logic as with variables. Variable names in essence are just placeholders. So what we need (in some sense) is to define multiple placeholders for the same memory location. That is exactly what references are – additional placeholders for the same memory location.
The following pseudocode shows how we can define a reference:
<data_type> & <reference_name> = <variable>;
You need to keep in mind the following when using references:
  • Naming references follows the same convention as naming variables
  • References must be defined (you cannot just declare a reference)
  • References must have the same type as the variable they are referencing
  • Once a reference to a variable is set, it cannot be changed
Let’s look at an example:
int x = 2001;
int& y = x; //y is now a reference of x
x++; // x is now 2002
y++; // y is a reference to x, so x is incremented
cout << x; // Output: 2003
int z = 3;
y = z; //This will not redefine the reference, but assign 3 to x
cout << x; // Output: 3

Table of contents

Leave a Reply