Pointeurs

Motivation

struct human {
        int x;
        int y;
        int souffle;
};

void deplacerDroite(struct human a) {
    a.x++;
    a.souffle++;
}

struct human player; 

void game_loop() {
    ...
    if(isKeyRight())
        deplacerDroite(player);
    ...
}

Que pensez-vous du programme ci-dessus ? La variable globale player n'est pas modifée quand on appuie sur la flèche de droite. Le contenu de player est copié quand on appelle deplacerDroite donc seul l'argument a, qui est une copie de player est modifié.

Solution : un pointeur

Un pointeur est une variable contenant une adresse mémoire.

void deplacerDroite(struct human *a) {
    (*a).x++;
    (*a).souffle++;
}

deplacerDroite(&player);
AdresseDonnées
adresse*adresse
&datadata

On a déjà vu cette solution pour les tableaux car on ne pouvait de toute façon pas faire autrement. Un tableau se dégrade en pointeur quand il est passé en argument :

void fillArray(int A[10]) {
 ...
}

est la même chose que

void fillArray(int A[]) {
 ...
}

qui est la même chose que

void fillArray(int *A) {
 ...
}

Exercice métaphorique

  • Si x est une maison, qu'est ce que &x ? L'adresse postale de x

  • Si x est un livre dans une bibliothèque, qu'est ce que &x ? Sa côte qui donne sa localisation (pièce, étagère, etc.)

  • Si x est une page dans un livre, qu'est ce que &x ? Le numéro de la page.

  • Si x est une variable déclaré par int x;, qu'est ce que &x ? L'adresse mémoire où on trouve l'entier x.


  • Si a est une adresse postale, qu'est ce que *a ? La maison, le garage, l'hôpital, l'ENS, etc. qui se trouve à l'adresse a

  • Si a est la côte d'un livre dans une bibliothèque, qu'est ce que *a ? Le livre en question dont la côte est a

  • Si a est une numéro de page d'un livre, qu'est ce que *a ? Le contenu de la page numéro a

  • Si a est un pointeur sur un entier, déclaré par int *a;, qu'est ce que *a ? *a désigne l'entier que l'on peut lire à l'adresse mémoire a.

Déclaration

Déclarer un entier

int a;

a est une variable contenant un entier.

Déclarer un pointeur sur un entier

int *p;

p est une variable contenant une adresse mémoire, à partir de laquelle on interprète le contenu comme un entier noté *p.

Combien de bits prend p en mémoire ? 64 sur les systèmes 64 bits.

Combien d'octets prend p en mémoire ? 8 octets sur les systèmes 64 bits.

Exercices

    int x = 42;
    int y;
    y = x;
    y++;

Que vaut x ? L'instruction y = x réalise une affectation de la valeur de x dans y. C'est une copie. A la fin, y vaut 43 mais x vaut toujours 42.

    int x = 42;
    int *y;
    y = &x;
    (*y)++;

Que vaut x ? L'instruction y = &x réalise une affectation de l'adresse de x dans y. A la fin, *y, qui n'est autre que x vaut 43. Bref, x vaut 43.

Placement de l'étoile

On peut écrire

    int* p;

et

    int *p;

Malheureusement, il vaut mieux écrire attacher l'étoile au nom de la variable. Par exemple :

int *p, i;  // OUI

ou de manière équivalente

int* p, i;  // NON car on pourrait croire que p et i sont de même type

déclare p comme un pointeur sur un int alors i est vraiment un int. Mieux encore, évitez les déclarations sur une seule ligne et écrivez :

int *p; // TRES CLAIR
int i;  // TRES CLAIR

Typage

Toutes les variables suivantes sont des pointeurs :

int *p1;
float *p2;
struct point *p3;
...

Quelles est la taille de p1 ? de p2, de p3 ? Il sont tous sont de taille 8 octets (sur système 64 bits), de quoi écrire une adresse.

Il y aussi le type générique

void*

qui veut dire pointeur sur n'importe quoi.

void *p;

Cela veut dire pointeur vers n'importe quoi, et c'est à nous de savoir ce qu'il y a derrière. Le type void nous empêche d'interpréter les données pointées. Pour cela, on introduit une nouvelle variable :

void f(void *p)
{
    int* pInt = p;

    /*
    j'utilise ici pInt qui est un pointeur sur un entier
    */
}

ou alors on caste :

void f(void *p)
{
    int x = *((int*) p) + 1;

    /*
    j'utilise ici pInt qui est un pointeur sur un entier
    */
}

Scoop, quel est la taille d'un void* ? Toujours 8 octets (sur système 64 bits), de quoi écrire une adresse.

Pointeur nul

Il y a une valeur particulière NULL (qui vaut 0). Elle signifie que le pointeur pointe sur rien. Le comportement normal d'un pointeur est implicitement d'un type optionel :

  • soit il n'y a pas de données (et on pointe vers NULL)
  • soit on pointe vers une donnée

Supposons que p est le pointeur nul, combien d'octets faut-il pour stocker p ? Toujours 8 sur 64 bits !

C. A. R. Hoare a dit en 2009 :

I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn't resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.

Blog à lire : https://lucid.co/techblog/2015/08/31/the-worst-mistake-of-computer-science