• Aucun résultat trouvé

La gestion dynamique de la mémoire Il arrive fréquemment en programmation que les besoins effectifs en mémoire ne soient pas connus au moment

Exemple 2 Allocation et initialisation

La gestion dynamique de la mémoire Programmation orientée objet (C++) _________________________________________________________________________________________________________________ Allocation des tableaux

Tableau à une dimension

Pointeur = new type_de_donnees[Nb d'élements];

Exemple :

int* p = new[10]; ou également int* p;

p = new int[10];

• L'accès aux éléments du tableau peut se faire par p[i] ou par *(p+i).

• L'initialisation d'un tableau dynamique lors de l'allocation n'est pas permise.

int* p = new int[5] (1,2,3,4,5); // ERREUR

Tableau à plusieurs dimensions

Pointeur = new type_de_donnees[n][Cst1][Cst2]…

Seule la première dimension peut être variable, les dimensions restantes doivent être constantes, donc connues.

Exemple :

int (*Z)[4] = new int [3][4] ⇔ int Z[3][4];

L'opérateur delete

L'opérateur delete libère un espace mémoire déjà alloué par new. Il possède une syntaxe double qui différencie les objets isolés des tableaux.

Libération d'un objet isolé : delete pointeur;

Exemple :

#include <iostream.h> void main()

{

long* L = new long;

cout<<"Donnez un nombre entier"; cin>>*L;

cout<<"Le nombre saisi est:"<<*L; delete L;

}

Libération des tableaux dynamiques : delete[] pointeur;

Remarque :

Pour les tableaux de type de base les crochets peuvent être omis. Ce n'est pas le cas par contre pour les tableaux d'objets. Exemple : #include <iostream.h> void main() { int i, *p; p = new int [10]; for(i=0;i<10;i++) *(p+i) = i; for(i=0;i<10;i++) cout<<*(p+i); delete[] p; }

La gestion dynamique de la mémoire Programmation orientée objet (C++) _________________________________________________________________________________________________________________

Allocation dynamique d'un tableau à deux dimensions

malloc et new ne permettent de créer que des blocs à une dimension (des vecteurs). Alors pour créer une matrice de L lignes et C colonnes l'idée consiste à créer L vecteurs comportant chacun C éléments.

Le schéma suivant montre la représentation en mémoire de la structure dynamique à créer.

Allocation de la matrice

Dans le code suivant T désigne un type quelconque, prédéfini ou personnalisé :

Accès à un élément de la matrice

L'accès à un élément d'indice (i,j) se fait comme suit : *(*(M+i)+j) Ł M[i][j]

Libération de la matrice

Le code suivant permet de libérer la matrice :

Il faut toujours commencer par libérer les lignes puis on libère la table qui stocke l'adresse de ces lignes.

Variable auto vs variable dynamique

• Une variable auto est une variable dont la durée de vie est gérée d'une manière automatique par le système. Une telle variable est allouée sur la pile (empilée) suite à l'exécution de l'instruction qui la déclare. Elle sera ensuite automatiquement libérée (dépilée) lorsque l'exécution du programme atteint la fin du bloc d'instructions dans lequel elle est déclarée.

• Une variable dynamique est une variable dont la durée de vie est gérée par le programmeur. Une telle variable est allouée sur le tas (et non sur la pile) par un appel explicite à new (ou malloc). Elle ne sera libérée de la mémoire qu'à travers un appel explicite à delete (ou free) ou suite au redémarrage du système.

• Les variables dynamiques ne possèdent généralement pas de noms. Le seul moyen permettant de les manipuler et leur adresse en mémoire (renvoyée par new ou malloc). Cette dernière doit alors être préservée dans un pointeur approprié.

T** M T* T* T* T* C éléments de type T L lignes // Version C++ M= new(T*)[L]; for(int i=0; i<L;i++) *(M+i)=new T[C];

/* Version C */

M= (T**)malloc(L*sizeof(T*)); for(int i=0; i<L;i++)

*(M+i)= (T*)malloc(L*sizeof(T));

// Version C++ for(int i=0; i<L;i++) delete[] *(M+i); delete[] M;

/* Version C */ for(int i=0; i<L;i++) free(*(M+i)); free(M);

La gestion dynamique de la mémoire Programmation orientée objet (C++) _________________________________________________________________________________________________________________ Utilité de l'allocation dynamique de la mémoire

• Les espaces mémoire dynamiques sont utiles :

o pour créer des variables dont la taille est inconnue au moment de la compilation. C'est le cas par exemple lorsqu'il s'agit de créer un tableau dont le nombre d'éléments sera déterminé au moment de l'exécution par l'utilisateur.

o Pour créer des variables de très grande taille qui dépasse ce que peut accepter la pile d'exécution (c'est le cas des tableaux de grande taille par exemple). L'allocation des espaces dynamiques s'effectue sur le tas. Ceci fait que la seule contrainte concernant leur taille soit la disponibilité de la mémoire vive physique (taille de la RAM disponible). La taille de la pile d'exécution quant à elle reste limitée et dépend généralement des compilateurs. Elle peut être paramétrée dans les options de ces derniers.

Problème de fuite de la mémoire

Une zone mémoire dynamiquement créée mais non explicitement libérée va persister dans la RAM même après la fin de l'exécution du programme. Ce phénomène est communément appelé : fuite de mémoire.

Le programme suivant donne une illustration de ce phénomène.

01- void main() 02- { 03- int i; 04- int* p; 05- p= new int; 06- i=5; 07- *p=6; 08- cout<<"i :"<<i<<" et *p :"<<*p; 10- }

Ce programme se compile avec succès. Il s'exécute également d'une manière normale. Toutefois à sa sortie il engendre une fuite de mémoire. Pour détecter cette fuite nous allons analyser l'état de la mémoire qu'il utilise à différents niveaux d'exécution.

Etat de la mémoire suite à l'exécution de l'instruction de la ligne 4

i et p sont deux variables de type "auto". Elles sont allouées sur la pile d'exécution. Etat de la mémoire suite à l'exécution de l'instruction de la ligne 5

new int va engendrer la création d'une variable dynamique dont l'adresse est stockée dans p. Etat de la mémoire suite à l'exécution de l'instruction de la ligne 7

Le 5 est placé dans i et le 6 dans la zone dynamique pointée par p.

Pile

FF0EA5C

i p

Espace mémoire dynamiquement créé

Pile 5 FF0EA5C i p 6 i p Pile

La gestion dynamique de la mémoire Programmation orientée objet (C++) _________________________________________________________________________________________________________________

Etat de la mémoire suite à l'exécution de l'instruction de la ligne 10

La ligne 10 désigne la fin du programme. A ce niveau toutes les variables de type auto seront automatiquement libérées (i et p dans ce cas car leur portée est limitée au bloc du main) mais pas la zone dynamique pointée par p. Pour remédier à ce problème il faut libérer la zone pointée avant de quitter le programme.

01- void main() 02- { 03- int i; 04- int* p; 05- p= new int; 06- i=5; 07- *p=6; 08- cout<<"i :"<<i<<" et *p :"<<*p; 10- delete p; 11- }

Il est à noter que l'instruction delete p ne libère pas p (p étant de type auto) mais libère la zone mémoire dont l'adresse est stockée dans p.

Remarque :

La fuite de mémoire même si elle ne cause aucune perturbation directe du fonctionnement intrinsèque d'un programme peut être très nocive pour l'environnement dans lequel tourne ce dernier. En effet, l'espace mémoire non libérée va occuper inutilement une partie des ressources (RAM) de l'environnement. Ce problème est encore plus grave si le programme en question est sensé tourner sur un serveur en mode client/serveur. Dans ce cas, chaque client qui lance une exécution du programme va engendrer après sa sortie une occupation inutile d'une portion de la RAM du serveur. Au bout d'un certain nombre de connexions de clients, une grande partie de cette RAM se trouvera occupée sans être réellement utilisée. Ceci va conduire vers une chute des performances du serveur voire même son plantage.

Les fonctions Programmation orientée objet (C++) _________________________________________________________________________________________________________________

Les Fonctions