Memory management ***************** .. include:: ../BoutonGoogleTrad.rst 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 : .. code-block:: 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; } .. image:: pile.png :align: center :scale: 30% 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. .. warning:: 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 : .. image:: error.png :align: center :scale: 50% 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 : .. code-block:: 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 : .. image:: stackoverflow.png :align: center :scale: 50% 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. .. image:: memoire.png :align: center :scale: 50% 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 : .. panels:: :column: col-lg-5 p-2 .. raw:: html
Les mots-clefs new et delete SONT INTERDITS
.. raw:: htmlLes 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* : .. code-block:: 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++ modernesToutes les allocations dynamiques de mémoire se feront à travers l'utilisation de containers de la STL : vector, array... Quizzz ====== .. quiz:: mémoire :title: Gestion de la mémoire * :quiz:`{"type":"TF","answer":"T"}` En C++, il existe deux types de mémoire : la stack et le heap. * :quiz:`{"type":"TF","answer":"F"}` En C++, la taille du heap est fixé à l'initialisation du programme. * :quiz:`{"type":"TF","answer":"T"}` La stack est destinée à accueillir les variables temporaires. * :quiz:`{"type":"TF","answer":"T"}` Une erreur stack overflow se produit lorsque la mémoire de la stack est pleine. * :quiz:`{"type":"TF","answer":"F"}` 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. * :quiz:`{"type":"TF","answer":"T"}` La règle *RC-0* conseille de réutiliser les containers déjà fournis dans la STL.