DEV Community

TheStandardGuy
TheStandardGuy

Posted on

Pointers, a weird beast for beginners and beyond

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!
}
Enter fullscreen mode Exit fullscreen mode
Code example 1

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
}
Enter fullscreen mode Exit fullscreen mode
Code example 2

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
}
Enter fullscreen mode Exit fullscreen mode
Code example 3

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;
    }
}
Enter fullscreen mode Exit fullscreen mode
Code example 4

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)