Using C++ pointer syntax
The syntax to access memory in C++ is straightforward. The & operator returns the address of an object. That object can be a variable, a built-in type or the instance of a custom type, or even a function (function pointers will be covered in the next chapter). The address is assigned a typed pointer variable or a void* pointer. A void* pointer should be treated as merely storage for the memory address because you cannot access data and you cannot perform pointer arithmetic (that is, manipulate the pointer value using arithmetic operators) on a void* pointer. Pointer variables are usually declared using a type and the * symbol. For example:
int i = 42;
int *pi = &i;
In this code, the variable i is an integer, and the compiler and linker will determine where this variable will be allocated. Usually, a variable in a function will be on a stack frame, as described in a later section. At runtime, the stack will be created (essentially a chunk of memory will be allocated) and space will be reserved in the stack memory for the variable i. The program then puts a value (42) in that memory. Next, the address of the memory allocated for the variable i is placed in the variable pi. The memory usage of the previous code is illustrated in the following diagram:
The pointer holds a value of 0x007ef8c (notice that the lowest byte is stored in the lowest byte in memory; this is for an x86 machine). The memory location 0x007ef8c has a value of 0x0000002a, that is, a value of 42, the value of the variable i. Since pi is also a variable, it also occupies space in memory, and in this case the compiler has put the pointer lower in memory than the data it points to and, in this case, the two variables are not contiguous.
With variables allocated on the stack like this, you should make no assumptions about where in memory the variables are allocated, nor their location in relation to other variables.
This code assumes a 32-bit operating system, and so the pointer pi occupies 32 bits and contains a 32-bit address. If the operating system is 64 bits then the pointer will be 64 bits wide (but the integer may still be 32 bits). In this book, we will use 32-bit pointers for the simple convenience that 32-bit addresses take less typing than 64-bit addresses.
The typed pointer is declared with a * symbol and we will refer to this as an int* pointer because the pointer points to memory that holds an int. When declaring a pointer, the convention is to put the * next to the variable name rather than next to the type. This syntax emphasizes that the type pointed to is an int. However, it is important to use this syntax if you declare more than one variable in a single statement:
int *pi, i;
It is clear that the first variable is an int* pointer and the second is an int. The following is not so clear:
int* pi, i;
You might interpret this to mean that the type of both variables is int*, but this is not the case, as this declares a pointer and an int. If you want to declare two pointers, then apply * to each variable:
int *p1, *p2;
It is probably better just to declare the two pointers on separate lines.
When you apply the sizeof operator to a pointer, you will get the size of the pointer, not what it points to. Thus, on an x86 machine, sizeof(int*) will return 4; and on an x64 machine, it will return 8. This is an important observation, especially when we discuss C++ built-in arrays in a later section.
To access the data pointed to by a pointer, you must dereference it using the * operator:
int i = 42;
int *pi = &i;
int j = *pi;
Used like this on the right-hand side of an assignment, the dereferenced pointer gives access to the value pointed to by the pointer, so j is initialized to 42. Compare this to the declaration of a pointer, where the * symbol is also used, but has a different meaning.
The dereference operator does more than give read access to the data at the memory location. As long as the pointer does not restrict it (using the const keyword; see later), you can dereference the pointer to write to a memory location too:
int i = 42;
cout << i << endl;
int *pi { &i };
*pi = 99;
cout << i << endl;
In this code, the pointer pi points to the location in memory of the variable i (in this case, using the brace syntax). Assigning the dereferenced pointer assigns the value to the location that the pointer points to. The result is that on the last line, the variable i will have a value of 99 and not 42.