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;
}
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.i
, i.e. store the binary form of 5
in the memory locations starting at 0x000002.i
(0x000002) by using
the &
operator, and storing it in the variable ip
,
that is, in the memory locations starting at 0x000008.ip
(0x000002),
i.e., the same locations where variable i
is stored, therefore
the value of i
is changed.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:
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:
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. |
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.