Une variable locale disparaît, le tas reste
La pile et le tas sont les deux zones de mémoire où un programme C range ses variables. Savoir dans quelle zone vit une variable dit combien de temps elle existe et qui doit la libérer.
Un serveur de réservations tourne des semaines sans redémarrer. À chaque requête, il oublie de rendre dix octets. Au bout de quelques jours, la mémoire est pleine et le serveur s’arrête.
À la fin de cette leçon
Remarque
Je peux dire où vit une variable et ce que font malloc et free.
Mots-clés
| Mot | Ce que ça veut dire | Exemple |
|---|---|---|
| Pile | la zone qui garde les variables locales des appels en cours | |
| Cadre | l’espace que la pile réserve pour un appel de fonction | |
| Tas | la zone où on réserve des blocs à la demande | |
| malloc | la fonction qui réserve un bloc sur le tas | malloc(12) |
| free | la fonction qui rend un bloc au tas | free(p) |
| Fuite de mémoire | un bloc qu’on ne rend jamais alors qu’on ne s’en sert plus |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
La pile garde les variables locales d’un appel
Chaque appel de fonction reçoit un cadre sur la pile. Il contient les paramètres et les variables locales. Quand la fonction retourne, le cadre est retiré et ses variables n’existent plus.
Le prochain appel réutilise la même place avec de nouvelles variables. Une variable déclarée static fait exception : elle garde sa valeur d’un appel à l’autre.
Une variable locale et une variable static
int suivant(void) {
static int compte = 0;
int local = 0;
compte++;
local++;
return compte * 10 + local;
}Aller dans le dossier de travail
mkdir -p ~/c && cd ~/cAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Écrire le programme dans un fichier
printf '#include <stdio.h>\nint suivant(void) {\n static int compte = 0;\n int local = 0;\n compte++;\n local++;\n return compte * 10 + local;\n}\nint main(void) {\n int a = suivant();\n int b = suivant();\n int c = suivant();\n printf("%%d %%d %%d\\n", a, b, c);\n return 0;\n}\n' > pile.cAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Compiler et lancer le programme
gcc -Wall -o pile pile.c && ./pileAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Exemple travaillé : trois appels de suivant
| Appel | compte (static) | local au départ | Résultat |
|---|---|---|---|
| 1er | 1 | 0 puis 1 | 11 |
| 2e | 2 | 0 puis 1 | 21 |
| 3e | 3 | 0 puis 1 | 31 |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
malloc réserve un bloc, free le rend
Quand on ne connaît pas la taille à l’avance, ou qu’une donnée doit survivre à la fonction, on demande un bloc au tas avec malloc. La fonction rend l’adresse du bloc, ou NULL s’il n’y a plus de place.
Le bloc reste réservé jusqu’à l’appel de free. calloc fait comme malloc et met aussi tous les octets à zéro. Un bloc de malloc contient n’importe quoi au départ.
Réserver, remplir et rendre des blocs
int *p = malloc(3 * sizeof(int));
p[0] = 1; p[1] = 2; p[2] = 3;
free(p);
int *z = calloc(4, sizeof(int));Écrire le programme du tas
printf '#include <stdio.h>\n#include <stdlib.h>\nint main(void) {\n int *p = malloc(3 * sizeof(int));\n p[0] = 1;\n p[1] = 2;\n p[2] = 3;\n printf("%%d\\n", p[0] + p[1] + p[2]);\n free(p);\n int *z = calloc(4, sizeof(int));\n printf("%%d %%d\\n", z[0], z[3]);\n free(z);\n return 0;\n}\n' > tas.cAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Compiler et lancer le programme du tas
gcc -Wall -o tas tas.c && ./tasAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Trois erreurs classiques avec le tas
Une fuite : on oublie free, et la mémoire n’est jamais rendue. Un usage après free : on écrit dans un bloc déjà rendu, qui peut appartenir à autre chose. Un double free : on rend deux fois le même bloc, ce qui abîme les notes du tas.
Aucune de ces erreurs ne stoppe forcément le programme sur-le-champ. On ne les lance pas dans le labo : on les lit dans le tableau.
Les trois erreurs, sans les lancer
char *a = malloc(10); /* jamais free : une fuite */
char *b = malloc(10);
free(b);
b[0] = 'x'; /* usage après free */
free(b); /* double free */Exemple travaillé : ce que chaque erreur coûte
| Erreur | Ce qui se passe | Quand on le voit |
|---|---|---|
| Fuite | La mémoire utilisée grossit | Après des heures ou des jours |
| Usage après free | On écrit chez un autre bloc | Plus tard, ailleurs dans le programme |
| Double free | Le tas est corrompu | Souvent un arrêt immédiat, parfois bien après |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Écrire une fonction qui rend l’adresse d’un local
printf '#include <stdio.h>\nint *mauvais(void) {\n int local = 5;\n return &local;\n}\nint main(void) {\n printf("%%d\\n", *mauvais());\n return 0;\n}\n' > local.cAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Compter les avertissements de gcc
gcc -Wall -c local.c 2>&1 | grep -c "warning"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Erreur fréquente
Attention
Beaucoup de gens pensent qu’on peut renvoyer l’adresse d’une variable locale. Son cadre disparaît au retour : l’adresse désigne alors une place que le prochain appel va écraser. Renvoyez une valeur, ou un bloc de malloc que l’appelant rendra.