Jusqu'ici, tous nos tableaux avaient une taille statique immuable, figée au moment de la compilation (int tab[100];). Cette approche pose deux problèmes majeurs :
1. Gaspillage mémoire : Si votre programme n'utilise que 3 cases, les 97 autres monopolisent inutilement la mémoire.
2. Dépassement de capacité : Si un 101ᵉ utilisateur se présente, le programme plante ou refuse la donnée.
L'allocation dynamique résout ce dilemme : elle permet à votre programme de solliciter auprès du système d'exploitation la quantité exacte de mémoire nécessaire au moment de l'exécution (Runtime), selon le volume réel de données à traiter.
1. Pile (Stack) vs Tas (Heap)#
La mémoire vive attribuée à un programme C se divise en deux zones majeures :
flowchart TD
subgraph Memoire_Processus [Espace d'adressage virtuel d'un programme C]
Stack["PILE (Stack)\n- Mémoire automatique et ultra-rapide\n- Stocke les variables locales et cadres de fonctions\n- Taille limitée (1 à 8 Mo max)"]
Vide["... Espace libre d'extension ..."]
Heap["TAS (Heap)\n- Espace immense (plusieurs Go de RAM)\n- Réservé manuellement par le développeur\n- Durée de vie contrôlée manuellement avec free()"]
Data["SEGMENT DATA & CODE\n- Variables globales et binaires machine"]
end
Stack -->|Grandit vers le bas| Vide
Heap -->|Grandit vers le haut| Vide
- La Pile (Stack) : Gérée automatiquement par le compilateur. Les variables sont allouées à l'entrée d'une fonction et détruites dès sa sortie.
- Le Tas (Heap) : Géré manuellement par le programmeur. La mémoire allouée sur le tas persiste tant que vous n'appelez pas explicitement
free(), même si la fonction qui l'a créée s'est terminée !
2. Les 4 fonctions fondamentales de <stdlib.h>#
Pour manipuler le tas, nous utilisons la bibliothèque standard <stdlib.h> :
| Fonction | Rôle | Signature |
|---|---|---|
malloc() |
Réserve un bloc de \(N\) octets bruts (non initialisés). | void* malloc(size_t taille_octets); |
calloc() |
Réserve un bloc et initialise tous les octets à zéro. | void* calloc(size_t nb_elements, size_t taille_unitaire); |
realloc() |
Redimensionne un bloc existant en conservant son contenu. | void* realloc(void *ptr, size_t nouvelle_taille); |
free() |
Libère le bloc alloué et rend la mémoire au système. | void free(void *ptr); |
[!NOTE] Ces fonctions renvoient un pointeur générique
void *(un pointeur vers n'importe quel type de donnée). En C, unvoid *est implicitement converti vers le type du pointeur récepteur (pas besoin de cast explicite).
3. Allocation d'un tableau dynamique avec malloc()#
#include <stdio.h>
#include <stdlib.h> // Indispensable pour malloc et free
int main(void)
{
int n = 0;
printf("Combien d'etudiants souhaitez-vous enregistrer ? ");
scanf("%d", &n);
if (n <= 0)
{
printf("Nombre invalide.\n");
return 1;
}
// 1. ALLOCATION DYNAMIQUE : n * sizeof(float) octets sur le Tas
float *notes = malloc(n * sizeof(float));
// 2. VÉRIFICATION OBLIGATOIRE DU POINTEUR
if (notes == NULL)
{
fprintf(stderr, "Erreur critique : Memoire insuffisante sur le systeme !\n");
return 1; // Arrêt d'urgence du programme
}
// 3. UTILISATION NORMALE (comme un tableau ordinaire avec les crochets [])
for (int i = 0; i < n; i++)
{
printf("Note de l'etudiant %d : ", i + 1);
scanf("%f", ¬es[i]);
}
float somme = 0.0f;
for (int i = 0; i < n; i++)
{
somme += notes[i];
}
printf("Moyenne de la promotion : %.2f / 20\n", somme / (float)n);
// 4. LIBÉRATION OBLIGATOIRE DE LA MÉMOIRE
free(notes);
notes = NULL; // Bonne pratique pour éviter un pointeur sauvage
return 0;
}
4. calloc() : L'allocation propre avec initialisation à zéro#
Contrairement à malloc() qui laisse traîner des valeurs résiduelles en mémoire (garbage memory), calloc() (contiguous allocation) initialise tous les bits alloués à 0 :
// Alloue 1 000 entiers tous initialisés à 0
int *statistiques = calloc(1000, sizeof(int));
if (statistiques != NULL)
{
// statistiques[0] vaut 0 garanti !
free(statistiques);
statistiques = NULL;
}
5. realloc() : Ajuster la taille en cours de route#
Si votre tableau dynamique devient trop petit ou trop grand, realloc() ajuste sa dimension sur le tas tout en conservant les éléments déjà présents :
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
// Démarre avec 2 cases
int *tab = malloc(2 * sizeof(int));
if (tab == NULL) return 1;
tab[0] = 10;
tab[1] = 20;
// Agrandissement à 4 cases
int *nouveau_tab = realloc(tab, 4 * sizeof(int));
if (nouveau_tab == NULL)
{
// En cas d'échec de realloc, l'ancien bloc 'tab' existe toujours !
free(tab);
return 1;
}
tab = nouveau_tab; // Met à jour le pointeur
tab[2] = 30;
tab[3] = 40;
for (int i = 0; i < 4; i++)
{
printf("%d ", tab[i]); // Affiche : 10 20 30 40
}
printf("\n");
free(tab);
tab = NULL;
return 0;
}
6. Les 3 règles d'or de la gestion de mémoire#
Pour éviter les bugs les plus redoutés du génie logiciel :
- Toujours tester
if (ptr == NULL): Même sur des machines modernes, une allocation peut échouer si le système manque de mémoire ou si la taille demandée est corrompue. - À chaque
malloc/callocdoit correspondre unfree: Oublier de libérer la mémoire provoque une fuite de mémoire (Memory Leak) qui finira par asphyxier le système d'exploitation si votre programme tourne 24h/24 (serveurs web, services d'arrière-plan). - Mettre le pointeur à
NULLaprès lefree:free(ptr); ptr = NULL;. Cela évite les pointeurs suspendus (Dangling Pointers) et les catastrophes causées par une double libération (Double Free).
Vous maîtrisez désormais le contrôle absolu de la mémoire RAM en C. Mettons ces compétences en pratique dans la section d'applications qui suit.
Commentaires