🧠 Le cours : gérer la mémoire soi-même
Jusqu'ici, la taille de chaque tableau était fixée dans le code. Or un programme a souvent besoin de plus ou moins de place selon les données (un fichier, une saisie…). La mémoire dynamique permet de réserver cette place pendant l'exécution, puis de la rendre.
Pile et tas
PILE (stack) TAS (heap)
variables locales, paramètres blocs réservés avec malloc
créées et détruites vivent jusqu'au free
automatiquement à chaque appel (ou jusqu'à la fin du programme)
petite, très rapide grande, gérée à la main
malloc, sizeof et le test de NULL
malloc(octets) renvoie l'adresse d'un bloc de cette taille, ou NULL si la mémoire manque. On calcule la taille avec sizeof : n * sizeof(int) fonctionne partout, alors que n * 4 suppose qu'un int fait 4 octets. Il faut inclure <stdlib.h>. Le contenu d'un bloc fraîchement alloué est indéterminé ; calloc le remplit de zéros.
free : rendre la mémoire
free(p) rend le bloc au système. Après l'appel, l'adresse contenue dans p n'est plus valable : mets p = NULL pour que toute réutilisation par erreur soit détectée plus facilement. Un programme qui alloue sans jamais libérer a une fuite mémoire : il grossit jusqu'à saturer la machine.
Un tableau dont la taille dépend de l'utilisateur
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int n;
printf("Combien de notes ? ");
scanf("%d", &n);
if (n <= 0) {
return 1;
}
double *notes = malloc(n * sizeof(double));
if (notes == NULL) {
return 1;
}
double somme = 0;
for (int i = 0; i < n; i++) {
printf("Note %d : ", i + 1);
scanf("%lf", ¬es[i]);
somme += notes[i];
}
printf("moyenne = %.2f\n", somme / n);
free(notes);
return 0;
}
Si tu tapes 3, puis 12, 15 et 9Combien de notes ? 3
Note 1 : 12
Note 2 : 15
Note 3 : 9
moyenne = 12.00
realloc : agrandir un tableau
realloc(bloc, nouvelle_taille) change la taille d'un bloc en conservant son contenu (il peut le déplacer ailleurs). La technique habituelle consiste à doubler la capacité quand le tableau est plein : les agrandissements deviennent rares, donc le coût moyen d'un ajout reste faible. Range toujours le résultat dans un pointeur temporaire : si realloc échoue, l'ancien bloc existe encore et doit être libéré.
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int capacite = 2;
int taille = 0;
int *t = malloc(capacite * sizeof(int));
if (t == NULL) {
return 1;
}
for (int v = 1; v <= 5; v++) {
if (taille == capacite) {
capacite *= 2;
int *plus_grand = realloc(t, capacite * sizeof(int));
if (plus_grand == NULL) {
free(t);
return 1;
}
t = plus_grand;
}
t[taille++] = v * 10;
}
printf("taille = %d, capacite = %d\n", taille, capacite);
free(t);
return 0;
}
Affichagetaille = 5, capacite = 8
Dupliquer une chaîne
Pour copier un texte, il faut réserver sa longueur plus un (la place du '\0'), puis le recopier.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
const char *source = "NSI";
char *copie = malloc(strlen(source) + 1); /* +1 pour le '\0' */
if (copie == NULL) {
return 1;
}
strcpy(copie, source);
copie[0] = 'n';
printf("%s / %s\n", source, copie);
free(copie);
return 0;
}
Une liste chaînée
Quand on ne sait pas combien d'éléments on aura, on peut les relier entre eux : chaque maillon contient une valeur et l'adresse du suivant. Chaque maillon est alloué avec malloc ; à la fin, il faut les libérer un par un, en lisant l'adresse du suivant *avant* de libérer le maillon courant.
#include <stdio.h>
#include <stdlib.h>
typedef struct Noeud {
int valeur;
struct Noeud *suivant;
} Noeud;
Noeud *ajouter_devant(Noeud *tete, int v) {
Noeud *n = malloc(sizeof(Noeud));
if (n == NULL) {
return tete;
}
n->valeur = v;
n->suivant = tete;
return n;
}
int main(void) {
Noeud *liste = NULL;
for (int v = 1; v <= 3; v++) {
liste = ajouter_devant(liste, v);
}
for (Noeud *p = liste; p != NULL; p = p->suivant) {
printf("%d ", p->valeur);
}
printf("\n");
while (liste != NULL) { /* libération maillon par maillon */
Noeud *suivant = liste->suivant;
free(liste);
liste = suivant;
}
return 0;
}