Probleme – Compilation séparée : une pile en module
Exercice · niveau 3 (difficile) · informatique (MP2I/MPI), chapitre 1 — Le langage C
Énoncé
On veut offrir une pile d'entiers dont l'utilisateur ignore la réalisation.
- Écrire l'en-tête
pile.h, avec un type opaque et des gardes d'inclusion. - Écrire
pile.c, réalisation par tableau dynamique. - Expliquer ce que le type opaque interdit, et ce qu'il permet.
- Donner les commandes de compilation séparée.
Corrigé
1. L'interface.
/* pile.h */
#ifndef PILE_H
#define PILE_H
#include <stdbool.h>
typedef struct pile_s pile; /* type OPAQUE : la structure reste cachée */
pile* pile_creer(void); /* pile vide, ou NULL si échec */
bool pile_est_vide(const pile* p);
bool pile_empiler(pile* p, int x); /* false si l'allocation échoue */
int pile_depiler(pile* p); /* précondition : p non vide */
void pile_detruire(pile* p);
#endif
Les trois lignes #ifndef / #define / #endif rendent l'inclusion idempotente : inclure deux fois le fichier ne déclare qu'une fois.
2. La réalisation.
/* pile.c */
#include "pile.h"
#include <stdlib.h>
#include <assert.h>
struct pile_s { int* t; int n; int capacite; }; /* invisible à l'extérieur */
pile* pile_creer(void) {
pile* p = malloc(sizeof(pile));
if (p == NULL) { return NULL; }
p->t = malloc(4 * sizeof(int));
if (p->t == NULL) { free(p); return NULL; }
p->n = 0; p->capacite = 4;
return p;
}
bool pile_est_vide(const pile* p) { assert(p != NULL); return p->n == 0; }
bool pile_empiler(pile* p, int x) {
assert(p != NULL);
if (p->n == p->capacite) {
int* d = malloc((size_t) 2 * p->capacite * sizeof(int));
if (d == NULL) { return false; }
for (int i = 0; i < p->n; i = i + 1) { d[i] = p->t[i]; }
free(p->t); p->t = d; p->capacite = 2 * p->capacite;
}
p->t[p->n] = x; p->n = p->n + 1;
return true;
}
int pile_depiler(pile* p) {
assert(p != NULL && p->n > 0);
p->n = p->n - 1;
return p->t[p->n];
}
void pile_detruire(pile* p) { if (p != NULL) { free(p->t); free(p); } }
3. Ce que l'opacité interdit et permet.
Elle interdit : déclarer une pile sur la pile d'appel (sa taille est inconnue du compilateur, d'où l'allocation obligatoire), et lire ou écrire un champ depuis l'extérieur. Un utilisateur ne peut pas casser l'invariant : les seules fonctions qui touchent ces champs sont les cinq du module.
Elle permet : remplacer la réalisation par une chaîne de maillons, ou par un tableau à capacité fixe, sans recompiler ni relire une seule ligne du code utilisateur. C'est la modularité que le chapitre chap:abstraction détaillera, et elle se paye ici d'une indirection.
4. La compilation séparée.
gcc -Wall -Wextra -std=c99 -c pile.c -o pile.o
gcc -Wall -Wextra -std=c99 -c programme.c -o programme.o
gcc pile.o programme.o -o programme
Chaque .c est compilé séparément — d'où le nom — puis les fichiers objets sont liés. Modifier pile.c ne demande de recompiler que lui.
Les autres exercices de ce chapitre Le cours du chapitre
Un blocage sur cet exercice ? Le tuteur d'Adloun guide par questions, sans donner la réponse.