Pointeurs
Les pointeurs sont la propriété emblématique du C. Ils permettent de manipuler la mémoire. Mais attention, « un grand pouvoir implique de grandes responsabilités », les pointeurs rendent possible tout un nouvel éventail d'erreurs !
Définition
Un pointeur est une variable dont la valeur est une adresse mémoire. Le type de la valeur à cette adresse donne son type au pointeur. Ainsi on aura des pointeurs vers des entiers, des flottants…
Voici un exemple de représentation d'un pointeur p
vers un entier 17
.
On peut également représenter le pointeur dans la mémoire de l'ordinateur.
On a alors bien la case mémoire du pointeur p
qui contient l'adresse de l'entier 17
:
Déréférence
On peut accéder à la valeur pointée par un pointeur p
en utilisant l'opérateur *
.
On dit qu'on déréférence le pointeur.
Ainsi si p
est un pointeur vers int
, *p
sera un int
.
Déclaration
Comme on vient de voir que (dans le cas d'un pointeur vers un int
) *p
est un int
, il est possible d'écrire cette déclaration :
int *p;
qui est équivalent à (car les espaces ne sont pas significatifs en C) :
int* p;
On en déduit que le type « pointeur vers entier » est int*
Pointeur d'une variable
Nous avons vu qu'il était possible d'accéder à la variable ciblée par un pointeur avec l'opérateur *
.
Mais il est également possible de faire l'opération réciproque, c'est à dire obtenir un pointeur associé à une variable.
On utilise alors l'opérateur &
.
On dit qu'on obtient l'adresse de la variable.
Ainsi si a
est un int
alors &a
est un int*
(pointeur vers int
).
Il est ainsi possible d'obtenir un pointeur :
int a;
int *p = &a;
Malloc
Une deuxième façon d'obtenir un pointeur est d'utiliser la fonction malloc
de la bibliothèque stdlib
qui renvoie un pointeur vers un espace mémoire dont on donne la taille en argument.
Voici comment utiliser cette fonction pour obtenir un pointeur vers un entier :
int *p = malloc(sizeof(int));
Erreur de segmentation
Il ne faut jamais utiliser un pointeur non initialisé car il pointe vers une adresse aléatoire. Une telle utilisation entrainerait alors une erreur de segmentation qui arrêterait l'exécution du programme. L'erreur de segmentation se produit lorsqu'un programme essaie d'écrire sans autorisation dans une adresse mémoire.
#include <stdio.h>
int main() {
int *a;
*a = 1;
}
On pourra avoir besoin de déclarer des pointeurs pour les initialiser plus tard.
Dans ce cas, il est préférable de leur donner la valeur NULL
pour signaler qu'il n'est pas initialisé.
Et on testera si le pointeur vaut NULL
avant de l'utiliser :
#include <stdio.h>
int main() {
int *a = NULL;
if (a != NULL) {
*a = 1;
}
}
Le programme ci-dessus ne fait rien, mais il s'exécute correctement. Contrairement au programme ci-dessous :
#include <stdio.h>
int main() {
int *a;
if (a != NULL) { // a n'est pas NULL
*a = 1;
}
}
Exercices
1) Écrire une fonction void incr(int *pa)
qui incrémente un entier.
#include <stdio.h>
void incr(int* pa) {
*pa = *pa + 1;
}
int main() {
int a = 1;
incr(&a);
printf("%d\n", a);
}
2) Écrire une fonction void swap(int *pa, int *pb)
qui échange le contenu de deux entiers.
#include <stdio.h>
void swap(int *pa, int *pb) {
int c = *pa;
*pa = *pb;
*pb = c;
}
int main() {
int a = 1;
int b = 2;
printf("a : %d | b : %d\n", a, b);
swap(&a, &b);
printf("a : %d | b : %d\n", a, b);
}
3) Écrire une fonction void div_euclide(int a, int b, int *pq, int *pr)
qui écrit le quotient de a
et b
dans *q
et le reste dans *r
.
#include <stdio.h>
void div_euclide(int a, int b, int *pq, int *pr) {
*pq = a / b;
*pr = a % b;
}
int main() {
int a = 20;
int b = 3;
int q, r;
div_euclide(a, b, &q, &r);
printf("q : %d | r : %d\n", q, r);
}
4) En utilisant le marqueur %p
de printf
, afficher l'adresse mémoire de trois entiers déclarés préalablement. Que remarquez-vous ?
#include <stdio.h>
int main() {
int a, b, c;
printf("%p\n%p\n%p\n", &a, &b, &c);
}
On remarque que les adresses se suivent.
Elles sont séparée de 4 octets, ce qui correspond à la taille d'un int
.
Rien ne garantit se comportement même s'il est très probable.