Comment fonctionne la gestion de la mémoire en C et C++ ?

La gestion de la mémoire est un concept fondamental en programmation, surtout en C et C++, où elle est principalement manuelle. Contrairement aux langages modernes comme Python ou Java, C et C++ ne disposent pas de ramasse-miettes (Garbage Collector). Il est donc crucial pour le développeur de bien gérer l’allocation, l’utilisation et la libération de la mémoire.

Dans cet article, nous allons explorer :
✅ Les types de mémoire (stack, heap, statique)
✅ L’allocation et la libération en C (malloc, free)
✅ La gestion de la mémoire en C++ (new, delete, smart pointers)
✅ Les erreurs courantes et bonnes pratiques


1. Les types de mémoire en C et C++

En C et C++, la mémoire est divisée en trois principales zones :

1️⃣ La mémoire statique (Data Segment)

📌 Utilisée pour les variables globales et statiques.
📌 Allouée au démarrage du programme et libérée automatiquement à la fin.

cCopierModifierint global_var = 42;  // Variable globale, allouée dans la mémoire statique

void function() {
    static int counter = 0;  // Variable statique, persiste après la fin de la fonction
    counter++;
}

2️⃣ La pile (Stack)

📌 Utilisée pour les variables locales et les appels de fonctions.
📌 Gérée automatiquement par le compilateur.
📌 Chaque fonction a sa propre zone de pile (stack frame).
📌 Taille limitée (peut provoquer un stack overflow).

cCopierModifiervoid function() {
    int a = 10;  // Variable locale, allouée sur la stack
}

⚠️ Problème du stack overflow
Si une fonction s’appelle récursivement sans condition d’arrêt, elle peut épuiser la mémoire disponible sur la stack.

cCopierModifiervoid recursive_function() {
    int array[10000];  // Grosse allocation sur la stack
    recursive_function();  // Appel récursif infini -> Stack Overflow
}

3️⃣ Le tas (Heap)

📌 Utilisé pour l’allocation dynamique de mémoire.
📌 Doit être géré manuellement (malloc / free en C, new / delete en C++).
📌 Plus lent que la stack, mais permet d’allouer des blocs de mémoire de taille variable.

cCopierModifierint* ptr = (int*)malloc(10 * sizeof(int));  // Alloue un tableau de 10 entiers
free(ptr);  // Libère la mémoire

2. Allocation dynamique en C

🛠️ malloc (Memory Allocation)

Alloue un bloc de mémoire de taille donnée, mais ne l’initialise pas.

cCopierModifierint* ptr = (int*)malloc(5 * sizeof(int));  // Alloue un tableau de 5 entiers
if (ptr == NULL) {
    printf("Allocation échouée\n");
}

🛠️ calloc (Cleared Allocation)

Comme malloc, mais initialise la mémoire à zéro.

cCopierModifierint* ptr = (int*)calloc(5, sizeof(int));  // Alloue et initialise à zéro

🛠️ realloc (Resize Memory Block)

Permet de redimensionner un bloc de mémoire existant.

cCopierModifierint* ptr = (int*)malloc(5 * sizeof(int));
ptr = (int*)realloc(ptr, 10 * sizeof(int));  // Étend le tableau à 10 entiers

🛠️ free (Libérer la mémoire)

Libère un bloc de mémoire précédemment alloué avec malloc, calloc ou realloc.

cCopierModifierfree(ptr);  // Libération
ptr = NULL; // Évite les accès mémoire invalides

3. Allocation dynamique en C++ (new et delete)

C++ fournit des opérateurs new et delete pour gérer l’allocation dynamique plus facilement qu’en C.

🔹 new (Allocation d’un objet unique)

Crée un objet en mémoire dynamique et retourne un pointeur.

cppCopierModifierint* ptr = new int(42);  // Alloue un entier avec la valeur 42
delete ptr;  // Libération

🔹 new[] (Allocation d’un tableau)

Pour allouer un tableau dynamique, on utilise new[].

cppCopierModifierint* array = new int[5];  // Tableau de 5 entiers
delete[] array;  // Libération

🔹 delete et delete[]

⚠️ Utiliser delete[] pour les tableaux, sinon comportement indéfini !


4. Les erreurs courantes en gestion de mémoire

❌ Fuites de mémoire (Memory Leak)

Oublier d’appeler free ou delete entraîne une consommation excessive de mémoire.

cppCopierModifiervoid memory_leak() {
    int* ptr = new int(42);
    // Oubli de `delete ptr;` => Mémoire jamais libérée
}

❌ Double libération (Double Free)

Libérer une mémoire déjà libérée peut causer un segmentation fault.

cCopierModifierint* ptr = (int*)malloc(sizeof(int));
free(ptr);
free(ptr);  // ERREUR !

❌ Dangling Pointer (Pointeur Dangling)

Un pointeur qui pointe vers une mémoire libérée peut provoquer un comportement indéfini.

cCopierModifierint* ptr = (int*)malloc(sizeof(int));
free(ptr);
printf("%d", *ptr);  // ERREUR : accès mémoire illégal

❌ Dépassement de mémoire (Buffer Overflow)

Lire ou écrire en dehors des limites d’un tableau alloué peut provoquer des plantages.

cCopierModifierint* array = (int*)malloc(5 * sizeof(int));
array[5] = 10;  // ERREUR : accès hors limites !

5. Les Smart Pointers en C++ (Eviter les fuites de mémoire)

C++ moderne (C++11 et +) propose les smart pointers pour éviter les erreurs de gestion de mémoire.

unique_ptr (Pointeur unique, pas de copie)

cppCopierModifier#include <memory>

std::unique_ptr<int> ptr = std::make_unique<int>(42);  // Pas besoin de delete

shared_ptr (Référence partagée)

cppCopierModifier#include <memory>

std::shared_ptr<int> p1 = std::make_shared<int>(10);
std::shared_ptr<int> p2 = p1;  // p1 et p2 partagent la mémoire

weak_ptr (Référence sans ownership)

Utilisé pour éviter les cycles de références.


6. Conclusion et bonnes pratiques

✔️ Utiliser malloc / free en C et new / delete en C++.
✔️ Toujours libérer la mémoire après utilisation (free ou delete).
✔️ Éviter les fuites de mémoire en suivant la règle « Chaque new doit avoir un delete« .
✔️ Utiliser les smart pointers (unique_ptr, shared_ptr) en C++.
✔️ Toujours initialiser les pointeurs à NULL après free.

La gestion de la mémoire est cruciale pour optimiser les performances et éviter des erreurs difficiles à déboguer. Avec de bonnes pratiques, on peut écrire un code fiable et efficace ! 🚀

carle
carle