There is no construct of the C/C++ programming language more difficult for beginners than pointers. This is a fact, not an opinion. Many students struggle with this concept and many developers flee from the idea of handling with them. I think that to understand pointers, one should start from the beginning, therefore...
What is a pointer?
A pointer is a data type of the language like int
, float
etc. The syntax to declare a pointer is to put an *
after a type, so, for example, a pointer to an integer will be declared as int *
, a pointer to a custom structure, like say MyType
, will be declared as MyType *
.
Now that we know how to declare a pointer, we need to know what it is! A pointer, like the name itself says, is a type that points to something, but what? It points to an address of the type declared. Therefore, int *
stores the value of the address of an integer type, MyType *
stores the address of a MyType
variable.
Making a clearer example:
#include <iostream>
int main(){
// remembering that the address of a variable is
// accessible with operator &
int value{42};
int* p = &value;
std::cout << &value << std::endl; // 0x50527c (as example)
std::cout << p << std::endl; // 0x50527c, the address of value!
}
Principal pointers operators
Now that we know what a pointer is, besides assigning the address of another variable, what can we do with them? There are two main operations for pointers, dereferencing and what it is called pointer arithmetic.
Dereferencing
The operation of dereferentiation is done with the operator *
(again) before a variable of pointer-type. This operation allows us to access the value pointed to.
#include <iostream>
int main(){
int var{42};
// remembering that the address of a variable is
// accessible with operator &
int *p = &var; // p points to the memory address of var
*p = 21; // now var = 21
}
But what happens if I dereference an unassigned pointer? In cplusplus, regardless of type, an unassigned variable has a random value, so accessing it as a memory address can bring us many headaches. If we are lucky, the program crashes; otherwise, we can have undefined behaviours (UB) or memory corruptions (MC).
Why did I say a crash is a a good thing? Because we noticed it and we *must * intervene to resolve it, while with UB and MC there is the chance we won't notice it for a long time, and it can be very difficult to debug and resolve it.
Pointer arithmetic
Pointer arithmetic allows us to change the value of a pointer, hence, the address stored inside the variable. Like any other integral number, a pointer can be pre- and post- incremented ++
and decremented --
. Furthermore, a pointer can be added/substracted to/from an integer. One might think that incrementing a pointer by 1 points to the next address in memory, and this is not completely wrong, but it points to the next address distant from the previous one by the size of the type pointed to.
Then, in our example, p+=1
means that p
is now pointing the address distant 1*sizeof(int)
from the previous one.
This pointers feature should be used very consciously because it allows us to navigate the memory.
#include <iostream>
int main(){
// the memory is sequentially mapped so w is sizeof(int) bytes after v
int v{21};
int w{42};
int *p{&w}; // p points to w
p++; // now p points to next address ... but there is v there!
*p +=1; // adding 1 to the value in the address pointed by p
std::cout << v << std::endl; // print 22
std::cout << w << std::endl; // print 42
}
As we can see, we changed the value of a variable that was never pointed to explicitly by an assignement of a pointer variable, but with pointer arithmetic!
The zero of pointers
Like the number 0 represents a very special case for numbers, there is a similar value for pointers. In C++ it is nullptr
and, usually, a pointer with value nullptr
is called invalid. It is very important to use this value in a smart way, so we should assign a pointer to nullptr when:
- We have finished using it.
- To signal something has goes wrong.
These practices can be used to detect if a pointer is safe to dereference to avoid crashes, UB, or MC. Therefore, we should always check the validity of a pointer before dereferencing it.
But what happens if I forget to check the validity and deference a nullptr? With good chance we have a crash due to segmentation fault or access violation.
#include <iostream>
int* foo(int value){
// static variables live accross the function scope
static int answ{42};
if(answ == value){
return &answ;
} else {
return nullptr;
}
}
int main(){
int *p = foo(21);
// std::cout << *p << std::endl; // crash!
if(p){
std::cout << *p << std::endl;
} else {
std::cout << "p is invalid" << std::endl;
}
}
Key takeawayas
Summarizing we've learned that:
- Pointers are variable that store a memory location.
- Always initialize pointers, possibly with
nullptr
if no valid address is available. - Always check for
nullptr
before dereferencing to avoid crashes. - Be careful with pointer arithmetic.
This is what I would say about pointers for today but, for sure, this is not the last time I talk about them!
See you in the next episode!
Top comments (0)