Memory management

  • Your native language: with Chrome, right-click on the page and select « Translate into … »

  • English version:

Dans l’univers C++, il y a deux types de mémoire :

  • La pile (stack) stockant les variables locales et les paramètres des fonctions.

  • Le tas (heap) stockant les données allouées dynamiquement.

The stack (la pile)

Fonctionnement

Lors d’un appel de fonction, les paramètres et les variables locales de cette fonction sont créées sur la pile. Cela concerne aussi bien les types fondamentaux (int, float) que les types structurés comme les objets. Une fois la fonction terminée, ces variables sont libérées et retirées de la pile. Ainsi, apparaît un phénomène d’empilement et de dépilage des variables au fur et à mesure de l’exécution du programme. Voici un exemple :

struct Point
{
   int x,y;
   Point(int _x, int _y) { x=_x; y=_y; }
};

void test(int v)
{
   Point B(4,5);
   int k = v + B.x;
   std::cout << k;
}

int main()
{
   Point A(1,2);
   test(A.x);
   return 0;
}
../_images/pile.png

Que se passe-t-il :

  • Entrée dans main() : la variable locale A est créée dans la pile en tout premier.

  • Entrée dans test() : création de l’argument v et des deux variables locales B et k sur la pile.

  • Fin de test() : les trois variables v, B et k sont libérées.

  • Fin de main() terminée, la variable A est détruite et la pile est maintenant revenue à son état initial.

Avertissement

La pile ne permet pas de stocker des tableaux de taille variable, la taille doit être connue à la compilation. Ainsi la syntaxe suivante ne peut fonctionner :

../_images/error.png

La taille de pile

La pile est conçue pour stocker de petites variables, comme les indices de boucles ou les types fondamentaux. Au lancement du programme, un espace mémoire fixe lui est réservé : 1 Mo sous Windows et 8 Mo sous Linux et macOS. Elle n’a donc pas vocation à contenir de gros objets, au risque de compromettre son fonctionnement. Si jamais la pile est entièrement remplie, cela produit une erreur générale appelée stack overflow et le programme s’interrompt brutalement.

Note

A noter que cette erreur Stack overflow a donné son nom au plus grand fameux forum de développeurs !

Cela peut-il vous arriver ? Généralement, soit en faisant une récursion infinie, soit en écrivant ceci :

int T[400][400];

Cette ligne, plutôt banale, a cependant pour effet de consommer 400x400x4 = 640ko de mémoire, soit les 2/3 de la taille de la pile sous Windows ! Il suffit d’agrandir un peu la taille de ce tableau pour faire crasher le programme :

../_images/stackoverflow.png

The heap (Le tas)

Rôle

Pourquoi allouer des données sur le tas :

  • Le volume des données est variable.

  • Le volume des données est important.

  • La durée de vie des données s’étend au delà de la fonction qui les a créées.

Le tas n’a pas de taille maximale imposée par le compilateur. Cette fois, sa seule limite est la mémoire RAM disponible sur l’ordinateur.

Les fuites mémoire

Vous n’avez peut-être jamais entendu cette expression, mais pour le programmeur C++, c’est le stress absolu !! En effet, la gestion des données sur le tas est plus complexe car le programmeur doit gérer :

  • L’allocation des données en écrivant une syntaxe spécifique.

  • La libération des données en utilisant une syntaxe spécifique aussi.

L’allocation ne pose généralement pas de problème car si elle n’est pas faite le programme ne va pas fonctionner et le problème se voit immédiatement. Par contre, si la libération est oubliée, tout va continuer à fonctionner normalement, d’où le problème. Si de plus, cet oubli se produit dans une boucle, alors de la mémoire non libérée va s’accumuler jusqu’à ce qu’il n’y ait plus de mémoire disponible ce qui provoquera l’arrêt du programme plusieurs minutes ou plusieurs jours après son lancement.

Voici un graphique montrant l’accroissement de la taille mémoire consommée par le programme à cause d’une fuite mémoire. Lorsque la mémoire totale est atteinte, le processus est tué par l’OS ce qui libère la mémoire immédiatement.

../_images/memoire.png

Stratégie

Le programmeur C++ moderne évite d’utiliser cette gestion manuelle des allocations/libérations de mémoire. Depuis C++11, il dispose d’autres outils lui permettant de contourner ces anciennes mécaniques. Vous êtes obligés de rompre avec l’utilisation des mots-clefs :

  • new et delete pour les habitués du C++ 98

  • malloc et free pour les habitués du langage C

Pour être clair, au risque de se répéter :

Les mots-clefs new et delete SONT INTERDITS

Les fonctions malloc et free SONT INTERDITES

La solution

Un design innovant

Pour gérer toutes notres contraintes d’allocation mémoire dynamique, nous allons utiliser la classe template vector. Pourquoi un tel choix ? A ceci, plusieurs raisons :

  • Les opérations de copie/redimensionnement sont déjà implémentées.

  • La taille d’un objet vector peut s’adapter dynamiquement en fonction des besoins.

  • La classe vector est optimisée pour des opérations complexes.

  • La libération de la mémoire est gérée automatiquement.

Ne sous-estimez pas la complexité de la gestion des ressources mémoire. Prenons un exemple avec deux objets de type Matrice :

M1 = M2;        // copy into an already existing object

Voici une liste non exhaustive des difficultées :

  • Personne n’a dit que la taille des deux matrices étaient identiques.

  • Il faut penser à libérer les données contenues dans M1.

  • Il ne faut pas faire un lien de M1 vers les donneés de M2, car si M2 est détruite…

  • La copie des éléments de M2 vers M1 en utilisant des boucles for est loin d’être l’approche la plus rapide.

Conclusion

Utiliser un objet vector pour stocker des données est un choix sûr et c’est aussi un choix synonyme d'efficacité et de sécurité au sens où toutes les configurations piégeuses ont été traitées et optimisées. D’ailleurs, Bjarne Stroustup en personne, a édicté un principe de développement connu sous le nom de règle RC-0. Cette règle conseillée pour les développeurs C++ modernes précise que :

Règle RC-0 : Si vous pouvez éviter de définir des opérateurs pour gérer des situations complexes - FAITES LE !

Ainsi, si dans la STL existe une classe répondant à vos besoins, ne réinventez pas la roue ! En conclusion de ce chapitre :

Toutes les allocations dynamiques de mémoire se feront à travers l'utilisation de containers de la STL : vector, array...

Quizzz

  • En C++, il existe deux types de mémoire : la stack et le heap.

  • En C++, la taille du heap est fixé à l’initialisation du programme.

  • La stack est destinée à accueillir les variables temporaires.

  • Une erreur stack overflow se produit lorsque la mémoire de la stack est pleine.

  • Si les variables a,b,c et d sont créées dans cet ordre sur la stack, alors la variable a sera la première détruite.

  • La règle RC-0 conseille de réutiliser les containers déjà fournis dans la STL.