Boris Eng

ATER at Université Sorbonne Paris Nord, France [last name][first name]@hotmail.fr
« Return to Teaching

Entraînement sur les pointeurs en C

Les pointeurs permettent de gérer nous-même certaines opérations sur la mémoire et d’avoir plus de flexibilité lorsqu’on programme. Les utiliser demande une grande rigueur pour éviter de créer des problèmes de mémoire (SEGFAULT, accès non autorisé à la mémoire, accès en dehors des bornes d’un tableau etc).

Tableaux dynamiques

Les tableaux en C sont statiques : on les utilise lorsqu’on traite un nombre spécifique de données dont les bornes sont connues. Avec les pointeurs, on peut simuler un tableau nous-même. Comparons les deux approches :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdlib.h>
#include <stdio.h>

int main()
{
	// Approche par tableau statique

	int tab[5] = {1, 2, 3, 4, 5};
	tab[0] = 10;
	printf("tab[0] = %d\n", tab[0]);
	printf("adresse de tab[0] = %p\n", &tab[0]);
	
	// Approche par pointeurs

	// pointeur pour lequel on autorise une zone de 5 blocs d'entiers dans la mémoire

	int* tab2 = (int*)malloc(5*sizeof(int));
	*tab2 = 1; // premier élément du tableau

	*(tab2+1) = 2; // deuxième élément du tableau : celui à l'adresse de tab2 + 1 bloc

	*(tab2+2) = 3;
	*(tab2+3) = 4;
	*(tab2+4) = 5;
	*tab2 = 10;
	printf("tab2[0] = %d\n", *tab2);
	printf("adresse de tab2[0] = %p\n", tab2);

	return EXIT_SUCCESS;
}

Ecrire les fonctions suivantes :

  1. void reverse(int* tab, int taille) qui inverse les éléments d’un tableau représenté par un pointeur.

  2. void fusion(int* tab1, int* tab2, int taille1, int taille2) qui colle tab2 à la suite de tab1.

Faisons une petite expérience. Imaginons que nous représentons des messages avec des chaînes de caractère de type char* se terminant par \0. On représente une boîte aux lettres par un pointeur sur des messages. On a donc ces types :

1
2
typedef char* message;
typedef message* boite;

Ecrire les fonctions suivantes :

  1. int est_vide(boite b) qui renvoie 1 si la boite est vide et 0 sinon.

  2. void ajouter_message(boite b, message m) qui ajoute le message m à la boîte b.

  3. void lire(boite b) qui affiche le premier message puis le jette.

  4. void jeter(boite b) qui jette le premier message sans le lire.

  5. void lire_tout(boite b) qui lit tous les messages d’une boîte en jetant les lettres à chaque fois.

Pointeurs et structures : listes chaînées

Une liste chaînée est une liste formée de noeuds indépendants qui sont liés ensemble. On n’y accède pas par des indices mais en suivant une chaîne de pointeurs. Un avantage est que les éléments ne sont pas à la suite dans la mémoire. Il est facile de supprimer un élément ou d’en ajouter un au milieu. On va utiliser le type suivant :

1
2
3
4
5
6
struct Noeud
{
	int valeur;
	struct Noeud* suivant;
};
typedef struct Noeud Noeud;

Voici un exemple de liste chaînée contenant 1, 2 et 3 à la suite.

1
2
3
4
Noeud n3 = {3, NULL};
Noeud n2 = {2, &n3};
Noeud n1 = {1, &n2};
Noeud* tete = &n1;

Ecrivez les fonctions suivantes :

  1. void afficher(Noeud* tete) qui prend la tête d’une liste chaînée et affiche tous les éléments qui la suivent.

  2. void ajouter_debut(Noeud* tete, Noeud* new) qui ajoute un nouvel élément en tête de la liste chaînée.

  3. void ajouter_fin(Noeud* tete, Noeud* new) qui ajoute un nouvel élément à la fin de la liste chaînée.

  4. void supprimer(Noeud* tete, int v) qui supprime tous les éléments qui ont pour valeur v.