Pointers

Pointer types are data types with a value set consisting of the memory addresses of data elements of a certain type. In other words, you can store a memory address into a pointer-type variable (pointer). So, in a pointer, you can store the location of an important piece of data within the memory.

As we learned earlier, this is how the C++ pointers are defined:

target_type* variable;

which is similar to the usual definition of the variable of the target type, but you add a * between the name of the variable and the type of the variable.

The resulting variable is able to store the memory address (location within the memory) of the target type variable. Target type is essential, because it enables the program to handle the data (bits) stored in the pointer’s memory address correctly.

When necessary, you can find out the memory address of any of the program’s variables by using the unary operator &. With the unary operator *, you can access the contents of the storage location at the memory address which is stored in the pointer. The idea of the unary operator * here is analogous to its use with iterators.

Let us have a look at a simple example:

#include <iostream>

int main() {
    int i;
    int* ip = nullptr;  // We are going to get back to nullptr at the end of this section
    i = 5;
    ip = &i;
    *ip = 42;
    std::cout << i << "," << *ip << "," << ip << "," << &ip << std::endl;
}
Creating the integer variable i, i.e. allocating space for it in a free location in the memory. Let us now imagine that we allocate memory at the address 0x000002.
Creating a pointer to the integer ip, i.e. allocating space for it in a free location in the memory. Let us imagine that we now allocate memory at the address 0x000008.
Assigning the value 5 to i, i.e. store the binary form of 5 in the memory locations starting at 0x000002.
Finding out the address of the variable i (0x000002) by using the & operator, and storing it in the variable ip, that is, in the memory locations starting at 0x000008.
Assigning the binary form of 42 into the memory locations starting at the address stored in ip (0x000002), i.e., the same locations where variable i is stored, therefore the value of i is changed.
Because the pointer ip is a variable, its location within the memory can be found with the & operator. In our imaginary example, the print on the screen is 42,42,0x000002,0x000008. The actual print will, of course, vary depending on the computer, operating system, and execution round.

The memory usage at the end of the main function would look like this:

../../_images/muisti_05_en.png

The example assumes that the pointer is 4 bytes, or 32 bits. In reality, in most modern computers, the pointers are 64 bits.

We can see in the example that when we use *ip in an expression, it works like an int-type variable: Its value is evaluated from the storage location to which ip points, and we can assign data into it with the operator =, which stores the value into the storage location in question. Pointers of other types work the same way.

The value of a pointer shows where a piece of data is located. Because the number of the storage location is not relevant in itself, the contents of the previous illustration are often presented in the following, simplified way:

../../_images/osoitinkuva1.png

By depicting the pointer with an arrow, we can show where it points to without repeating the index of the storage location, which is irrelevant. In other words, this picture is the same as the previous one, except that this one is more abstract as the storage location index has been left out and the pointing is depicted with an arrow.

With the use of the * operator, you can find out the actual pointed data from the pointer. It goes without saying (or does it?) that you can store a variable address only in a pointer with the same target type as the address type.

In the example, we initialize nullptr to the pointer ip. This is a small but important detail. A pointer like this is called the null or nil pointer. The special value nullptr can be assigned to all of the pointer variables. In this way you can set a pointer to point nowhere. You can compare the equality or inequality of a pointer to the nullptr value, and you can use nullptr as a pointer-type parameter. Often, the functions that have pointer as their return value type return nullptr when errors occur.

If you try to access [*] data through the null pointer during the execution of a program, it always results in an execution error and terminates the program. It is always good to use the null-pointer as the initializing value of pointer variables, and as the error-return value of functions with pointer as their return value type.

If you have stored the memory address of a class-type or struct-type value, the methods and fields of the pointed data are handled with the operator ->.

[*]This means examining the data to which the pointer points. In other words, to target the unary operator * or the operator -> at the pointer.