Allocation dynamique de la mémoire en c

26 Aug 2019 26 Aug 2019 12657 vues ESSADDOUKI Mostafa 10 min de lecture

Allocation dynamique de la mémoire en C

En C, la taille d'un tableau déclaré classiquement est fixée à la compilation. Si on déclare int tab[8] et qu'on n'utilise que 5 éléments, 3 cases sont gaspillées. Inversement, si on a besoin de 11 éléments, il n'y a pas de place. L'allocation dynamique résout ce problème en permettant de réserver et d'ajuster la mémoire à l'exécution.

Définition — Allocation dynamique de mémoire L'allocation dynamique est le mécanisme par lequel un programme réserve de la mémoire sur le tas (heap) pendant son exécution, selon les besoins réels. Contrairement aux variables locales (pile) et globales (segment de données), cette mémoire n'est pas libérée automatiquement : le programmeur est responsable de sa gestion.

Schéma — tableau de 8 éléments en mémoire, illustration du problème de taille fixe

C fournit 4 fonctions dans <stdlib.h> pour gérer l'allocation dynamique :

FonctionRôleInitialise à 0 ?Arguments
malloc()Allouer un bloc de mémoireNon (valeurs indéterminées)Taille en octets
calloc()Allouer n blocs initialisés à zéroOuiNombre d'éléments + taille d'un élément
realloc()Redimensionner un bloc existantNon (données conservées)Pointeur existant + nouvelle taille
free()Libérer un bloc allouéPointeur à libérer

1. malloc() — Allocation simple

Définition — malloc malloc (memory allocation) alloue un bloc contigu de n octets sur le tas et retourne un pointeur void * vers le début du bloc. Le contenu du bloc est indéterminé (valeurs quelconques).

Syntaxe — malloc C
#include <stdlib.h>

ptr = (type *) malloc(n * sizeof(type));

/* Exemple : tableau de 10 entiers */
int *ptr = (int *) malloc(10 * sizeof(int));
/* Alloue 10 × 4 = 40 octets sur le tas
   ptr pointe vers le premier octet du bloc */
Vérifier le retour de malloc Si la mémoire disponible est insuffisante, malloc retourne NULL. Toujours vérifier avant d'utiliser le pointeur :
int *ptr = (int *) malloc(n * sizeof(int));
if (ptr == NULL)
{
    fprintf(stderr, "Erreur : allocation échouée\n");
    exit(1);
}

Exemple n°1 — Allocation, remplissage et affichage avec malloc

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int *tab;
    int i;

    tab = (int *) malloc(5 * sizeof(int));

    if (tab == NULL)
    {
        printf("Mémoire non allouée.\n");
        exit(1);
    }

    printf("Mémoire allouée avec succès (malloc)\n");

    /* Remplissage */
    for (i = 0; i < 5; i++)
        tab[i] = i;   /* équivalent à *(tab+i) = i */

    /* Affichage */
    printf("Éléments : ");
    for (i = 0; i < 5; i++)
        printf("%d ", tab[i]);
    printf("\n");

    free(tab);
    tab = NULL;

    return 0;
}
Sortie
Mémoire allouée avec succès (malloc)
Éléments : 0 1 2 3 4

2. calloc() — Allocation avec initialisation à zéro

Définition — calloc calloc (contiguous allocation) alloue n éléments de taille donnée et initialise tous les octets à zéro. C'est l'équivalent d'un malloc suivi d'un memset(ptr, 0, …).

Syntaxe — calloc C
ptr = (type *) calloc(n, sizeof(type));

/* Exemple : tableau de 10 entiers initialisés à 0 */
int *ptr = (int *) calloc(10, sizeof(int));

Exemple n°2 — Allocation avec calloc : valeurs initiales à 0

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int *tab;
    int i;

    tab = (int *) calloc(5, sizeof(int));

    if (tab == NULL)
    {
        printf("Mémoire non allouée.\n");
        exit(1);
    }

    /* Avant modification — tous les éléments valent 0 */
    printf("Avant remplissage : ");
    for (i = 0; i < 5; i++)
        printf("%d ", tab[i]);
    printf("\n");

    /* Remplissage */
    for (i = 0; i < 5; i++)
        tab[i] = i * 2;

    printf("Après remplissage : ");
    for (i = 0; i < 5; i++)
        printf("%d ", tab[i]);
    printf("\n");

    free(tab);
    tab = NULL;

    return 0;
}
Sortie
Avant remplissage : 0 0 0 0 0
Après remplissage : 0 2 4 6 8

3. malloc vs calloc

Critèremalloccalloc
Nombre d'arguments1 — taille totale en octets2 — nombre d'éléments + taille d'un élément
InitialisationNon — valeurs indéterminées ("garbage")Oui — tous les octets à 0
VitessePlus rapide (pas de mise à zéro)Légèrement plus lente
Valeur de retourvoid * ou NULL si échecvoid * ou NULL si échec
Cas d'usageQuand on va remplir le bloc immédiatementQuand on a besoin d'un bloc initialement vide
Astuce — Quand choisir l'un ou l'autre ? Préférer malloc quand le bloc sera rempli immédiatement (copie, saisie…) — pas besoin de payer le coût de la mise à zéro. Préférer calloc quand on a besoin de partir de zéros, par exemple pour un tableau de compteurs ou une matrice nulle.

4. free() — Libérer la mémoire

Définition — free free(ptr) restitue au système le bloc mémoire pointé par ptr, qui doit avoir été alloué par malloc, calloc ou realloc. Après un free, le pointeur devient invalide (dangling pointer) — il faut le mettre à NULL.

Syntaxe — free C
free(ptr);
ptr = NULL;   /* bonne pratique — évite le pointeur dangling */
Comment free connaît-il la taille à libérer ? Lors de l'allocation, le gestionnaire de mémoire stocke la taille du bloc dans un mot caché juste avant l'adresse retournée. free(ptr) lit ce mot pour savoir combien d'octets restituer — d'où l'absence de paramètre de taille.

Exemple n°3 — Utilisation correcte de free

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int *tab;
    int i;

    tab = (int *) calloc(5, sizeof(int));

    if (tab == NULL) { printf("Allocation échouée\n"); exit(1); }

    for (i = 0; i < 5; i++)
        tab[i] = i;

    printf("Éléments : ");
    for (i = 0; i < 5; i++)
        printf("%d ", tab[i]);
    printf("\n");

    free(tab);     /* restitution de la mémoire */
    tab = NULL;    /* évite tout accès accidentel ultérieur */

    return 0;
}
Sortie
Éléments : 0 1 2 3 4
Danger — Double libération (double free) Appeler free(ptr)deux fois sur le même pointeur corrompt le gestionnaire de tas et peut provoquer un comportement indéfini ou une faille de sécurité :
free(ptr);
free(ptr);   /* comportement indéfini — NE PAS FAIRE */

/* Solution : mettre à NULL après free */
free(ptr);
ptr = NULL;
free(ptr);   /* free(NULL) est sans effet — sans danger */

5. realloc() — Redimensionner un bloc

Définition — realloc realloc modifie la taille d'un bloc précédemment alloué. Les données existantes sont conservées (jusqu'à l'ancienne taille). Si un déplacement est nécessaire, realloc copie automatiquement les données vers le nouvel emplacement et libère l'ancien bloc.

Syntaxe — realloc C
ptr = (type *) realloc(ptr, nouvelle_taille);

/* Exemple : agrandir un tableau de 5 à 10 entiers */
tab = (int *) realloc(tab, 10 * sizeof(int));
Attention — Ne pas écraser le pointeur original directement Si realloc échoue, il retourne NULL et l'ancien bloc n'est pas libéré. Écraser directement le pointeur original entraîne une fuite mémoire :
/* Incorrect — fuite si realloc échoue */
tab = (int *) realloc(tab, n * sizeof(int));

/* Correct — préserver l'accès à l'ancien bloc */
int *tmp = (int *) realloc(tab, n * sizeof(int));
if (tmp == NULL)
{
    fprintf(stderr, "realloc échoué\n");
    free(tab);   /* libérer l'ancien bloc */
    exit(1);
}
tab = tmp;

Exemple n°4 — Agrandir un tableau de 5 à 10 éléments avec realloc

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int *tab;
    int *tmp;
    int i, n;

    /* Allocation initiale — 5 éléments */
    tab = (int *) calloc(5, sizeof(int));
    if (tab == NULL) { printf("Allocation échouée\n"); exit(1); }

    for (i = 0; i < 5; i++)
        tab[i] = i;

    printf("Avant realloc : ");
    for (i = 0; i < 5; i++) printf("%d ", tab[i]);
    printf("\n");

    /* Agrandissement à 10 éléments */
    n = 10;
    tmp = (int *) realloc(tab, n * sizeof(int));
    if (tmp == NULL) { fprintf(stderr, "realloc échoué\n"); free(tab); exit(1); }
    tab = tmp;

    /* Remplissage des nouvelles cases */
    for (i = 5; i < n; i++)
        tab[i] = i;

    printf("Après realloc : ");
    for (i = 0; i < n; i++) printf("%d ", tab[i]);
    printf("\n");

    free(tab);
    tab = NULL;

    return 0;
}
Sortie
Avant realloc : 0 1 2 3 4
Après realloc : 0 1 2 3 4 5 6 7 8 9

6. Fuites mémoire

Définition — Fuite mémoire (memory leak) Une fuite mémoire survient lorsqu'un programme alloue de la mémoire sur le tas et ne la libère jamais. La mémoire perdue reste réservée jusqu'à la fin du processus. Les fuites sont particulièrement critiques dans les programmes de longue durée (serveurs, démons).

Exemple n°5 — Fuite mémoire : retour sans libération

#include <stdlib.h>

/* INCORRECT — fuite mémoire */
void test_fuite()
{
    int *ptr = (int *) malloc(sizeof(int));
    *ptr = 42;
    return;   /* ptr non libéré — 4 octets perdus à chaque appel */
}

/* CORRECT — libération avant le retour */
void test_correct()
{
    int *ptr = (int *) malloc(sizeof(int));
    if (ptr == NULL) return;
    *ptr = 42;
    free(ptr);   /* mémoire restituée */
    ptr = NULL;
}
Astuce — Détecter les fuites avec Valgrind L'outil Valgrind(Linux) détecte automatiquement les fuites mémoire et les accès invalides :
/* Compilation avec informations de débogage */
gcc -g programme.c -o programme

/* Exécution sous Valgrind */
valgrind --leak-check=full ./programme

Récapitulatif

FonctionSyntaxeInit à 0Point clé
mallocmalloc(n * sizeof(T))NonRapide — vérifier retour ≠ NULL
calloccalloc(n, sizeof(T))OuiUtile pour compteurs, matrices nulles
reallocrealloc(ptr, new_n * sizeof(T))NonUtiliser un pointeur temporaire
freefree(ptr); ptr = NULL;Toujours mettre à NULL après free
Règle d'orConséquence si non respectée
Un malloc = un freeFuite mémoire
Vérifier le retour de malloc/calloc/reallocDéréférencement de NULL → segfault
Ne jamais libérer deux fois le même pointeurCorruption du tas, comportement indéfini
Mettre ptr = NULL après freePointeur dangling — accès mémoire invalide
Utiliser un pointeur temporaire avec reallocPerte de l'ancien pointeur si échec → fuite

Discussion (0)

Soyez le premier à laisser un commentaire !

Laisser un commentaire

Votre commentaire sera visible après modération.