Logo de kxs.frCours d'informatique pour le lycée et la prépa

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.

p 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 :

adresses mémoire 0x7f40 0x7fa1 17 0x7f40 p

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.