Pointeurs C++ en une minute

Comme tout le monde, j'ai longtemps lutté pour comprendre comment fonctionnent les pointeurs en C++.

Puis un jour, j'ai lu une simple phrase qui m'a ouvert les neurones et permis, enfin, de saisir quand et pourquoi utiliser & et * pour manipuler les pointeurs.

Ceci n'est pas un tuto

Cher lecteur, ceci n'est pas un tutoriel sur les pointeurs, encore moins une explication de pourquoi ils existent et pourquoi les utiliser : c'est simplement un partage de cette explication succinte qui m'a remis les idées en place.

Variables et adresses

On stocke une valeur, 33, dans une variable nommée “num" :

int num;
num = 33;

C'est simple : num est une boîte, de type "int” (integer, un nombre entier), qui contient la valeur 33. Cette boîte est stockée en mémoire. Quelque part dans la mémoire. Où ? C'est l'ordinateur qui décide.

On peut lui demander :

int* address = #

Ici, address est une nouvelle variable, qui contient l'adresse de la variable num, et non pas la valeur de num.

Pourquoi ?

Parce que &num retourne un pointeur, un lien, alors que num retourne une valeur. Et ce pointeur se note avec *, donc on déclare int* (un pointeur vers un integer) et on lui assigne &num, qui pointe vers num.

On déclare le pointeur avec type* name, on récupère l'adresse de thing avec &thing, et on la stocke dans name.

Si on laisse le compilateur inférer le type, l'étoile disparaît mais on a exactement le même résultat :

auto address = #

Maintenant, la principale source de confusion vient du fait que cet opérateur étoile * est également ce qu'on utilise pour déréférencer un pointeur, c'est-à-dire pour retrouver la valeur vers laquelle pointe un pointeur.

En continuant notre exemple :

int result = *address;

Ici result est 33, parce que l'on a demandé non pas l'adresse elle-même, mais ce vers quoi elle pointe.

Attention donc à ne pas confondre l'usage de l'opérateur * s'il est placé avant ou après le = de l'assignation !

La révélation

Après ce petit rappel, voici la fameuse phrase qui m'a bien aidé.

Dans le contexte de la citation, a est une variable qui contient un pointeur.

Because &a means “the address of a” and *a means “the address that a is pointing to”.

Traduction :

Parce que &a signifie “l'adresse de a” et *a signifie “l'adresse vers laquelle a pointe”.

Et oui : un pointeur pointe vers une adresse, mais ce pointeur possède lui-même une adresse, et peut-être que le pointeur pointe vers un autre pointeur !

Ce n'est pas parce que l'on déréférence un pointeur (autrement dit, que l'on récupère la valeur vers laquelle il pointe) que ce que l'on obtient est une valeur - cela peut également être un pointeur.

L'exemple

Et maintenant un petit exemple qui relie les deux concepts exposés ci-dessus.

int num;
num = 33;
auto point = #
auto val = *point;

Que se passe-t-il ici ?

int num déclare une variable de type nombre entier

num = 33 assigne la valeur 33 à la variable nommée num

auto point = &num ici la variable point contient l'ADRESSE de la variable num

auto val = *point et enfin, la variable val contient 33 car on déréférence point et donc on obtient ce vers quoi il pointe

Donc point est un pointeur (une variable qui contient l'adresse d'une variable). En faisant *point on obtient num parce que point pointe vers l'adresse de num.

Mais nous aurions pu également faire auto wow = &point qui nous donnerait alors l'adresse du pointeur (pas le pointeur lui-même !). Cela entraînera la création de deux niveaux d'indirection entre wow et num. En effet wow pointe vers point qui pointe vers num qui contient 33.

Ouf !

Voici l'exemple complet :

int main()
{
    int num;
    num = 33;

    auto point = #
    auto val = *point;
    auto paddr = &point;
    auto pval = *paddr;
    auto unval = *pval;

    std::cout << num << std::endl;
    std::cout << point << std::endl;
    std::cout << val << std::endl;
    std::cout << paddr << std::endl;
    std::cout << pval << std::endl;
    std::cout << unval << std::endl;
}

Résultat :

33 // num

0x77787272fdf4 // point, l'addresse de num

33 // val, ce vers quoi pointe point, en fait num

0x77787272fdf8 // paddr, l'addresse du pointeur point lui-même

0x77787272fdf4 // pval, le contenu de paddr, donc l'addresse de num

33 // le déréférencement de pval, donc num

Voilà, ce n'est absolument qu'un aperçu de la surface de ce vaste et complexe sujet que sont les pointeurs, mais ça offre un angle de compréhension qui m'a fait faire un énorme progrès, et j'espère que cela vous sera également utile.

Auteur: Eric Dejonckheere