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 ?
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);
Adresse | Données |
---|---|
adresse | *adresse |
&data | data |
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é parint 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é parint *a;
, qu'est ce que*a
?*a
désigne l'entier que l'on peut lire à l'adresse mémoirea
.
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 ?
Combien d'octets prend p
en mémoire ?
Exercices
int x = 42;
int y;
y = x;
y++;
Que vaut x
? 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
? 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 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*
?
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
?
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