Essayer de coder ← Retour aux sujets

Réserver avec malloc

malloc / free

Les variables locales vivent sur la pile et disparaissent seules. Pour une taille connue à l'exécution, ou des données qui survivent à la fonction, on réserve sur le tas.

int *t = malloc(n * sizeof(int));
Réserve n entiers (contenu non initialisé)
int *t = calloc(n, sizeof(int));
Réserve n entiers mis à zéro
t = realloc(t, k * sizeof(int));
Agrandit ou réduit le bloc
if (t == NULL) { … }
Échec d'allocation : à tester à chaque fois
free(t); t = NULL;
Libère le bloc ; NULL évite de réutiliser l'ancienne adresse
dynamique.c
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int n = 5;
    int *carres = malloc(n * sizeof(int));

    if (carres == NULL) {
        printf("memoire insuffisante\n");
        return 1;
    }
    for (int i = 0; i < n; i++) {
        carres[i] = i * i;
    }
    for (int i = 0; i < n; i++) {
        printf("%d ", carres[i]);
    }
    printf("\n");
    free(carres);
    return 0;
}
Affichage
0 1 4 9 16

Les règles d'or

fuites

Chaque malloc doit être suivi d'un seul free. L'oublier crée une fuite mémoire ; le faire deux fois, ou utiliser le bloc ensuite, fait planter.

malloc ↔ free
Autant de libérations que d'allocations
free(p); free(p);
Double libération : comportement indéfini
free(p); *p = 1;
Utilisation après libération : pointeur fou
free(NULL);
Autorisé : ne fait rien
valgrind ./programme
Repère fuites et accès invalides (Linux)
Le réflexe

Écris le free en même temps que le malloc, avant même le code qui utilise le bloc. Tu ne l'oublieras pas.

🧠 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

moyenne_dyn.c
#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", &notes[i]);
        somme += notes[i];
    }
    printf("moyenne = %.2f\n", somme / n);

    free(notes);
    return 0;
}
Si tu tapes 3, puis 12, 15 et 9
Combien 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é.

realloc.c
#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;
}
Affichage
taille = 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.

copie_chaine.c
#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;
}
Affichage
NSI / nSI

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.

liste.c
#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;
}
Affichage
3 2 1

⚠️ Erreurs fréquentes

Oublier de libérer : la fuite mémoire

À éviter
faux.c
for (int i = 0; i < 1000; i++) {
    char *tmp = malloc(1000);
    /* ... utilisation ... */
}                  /* 1000 blocs perdus */
Correct
correct.c
for (int i = 0; i < 1000; i++) {
    char *tmp = malloc(1000);
    /* ... utilisation ... */
    free(tmp);     /* libéré à chaque tour */
}

Utiliser un bloc après l'avoir libéré

À éviter
faux.c
free(t);
t[0] = 1;          /* le bloc n'est plus à toi */
Correct
correct.c
free(t);
t = NULL;          /* toute erreur devient visible */

Libérer deux fois le même bloc (*double free*) est aussi un comportement indéfini. Mettre le pointeur à NULL après free évite ce piège, car free(NULL) ne fait rien.

Se tromper dans la taille demandée

À éviter
faux.c
int *t = malloc(n);              /* n octets seulement */
Correct
correct.c
int *t = malloc(n * sizeof *t);  /* n entiers */

Écrire n * sizeof *t calcule la taille de l'élément pointé : le code reste correct même si on change le type de t.

Écraser son propre pointeur avec realloc

À éviter
faux.c
t = realloc(t, k * sizeof(int));   /* si ça échoue, l'ancien bloc est perdu */
Correct
correct.c
int *tmp = realloc(t, k * sizeof(int));
if (tmp != NULL) {
    t = tmp;
}

Oublier le octet du caractère nul

malloc(strlen(s)) réserve une case de trop peu pour copier s : il faut strlen(s) + 1. L'écriture du '\0' déborde alors d'un octet, un bug difficile à repérer.

Ne pas tester le retour de malloc

Quand la mémoire manque, malloc renvoie NULL ; l'utiliser tel quel plante le programme. Dans les petits exercices on peut s'en passer, mais dans un vrai programme c'est un test obligatoire.

📋 Aide-mémoire

  • #include <stdlib.h> ; p = malloc(n * sizeof *p); ; test p == NULL ; free(p); p = NULL;.
  • calloc(n, taille) remplit de zéros ; realloc redimensionne (via un pointeur temporaire).
  • Un malloc ⇒ exactement un free ; jamais d'accès après free, jamais de double free.
  • Copier une chaîne : réserver strlen(s) + 1, puis strcpy.
  • Pile : variables locales, automatique, petite. Tas : malloc, manuel, grand.
  • Un outil pour vérifier : valgrind ./programme (Linux) ou gcc -fsanitize=address.