Programmation Structurée
Chapitre VI:
Allocation dynamique de la mémoire
Pr Bekkari 1
Cycle préparatoire de l’ENSA de Marrakech
Année universitaire 2024 - 2025 28/11/2024
Contenu du chapitre
1) Introduction
2) Allocation dynamique de la mémoire
3) Allocation dynamique et tableaux
4) Allocation dynamique et structures
5) Allocation dynamique et fonctions
28/11/2024
Introduction
• Quelle est la mémoire du travail d’une machine numérique ?
• Comment l’information est elle représentée dedans ?
• Dans un programme en C, avant de stocker n’importe quelle
donnée il faut lui allouer un espace mémoire.
Problème : lorsqu’on déclare par exemple un tableau de 21
étudiants, mais qu’on n’a finalement que 5 étudiants dans la
promotion!!! Alors gaspillage de mémoire.
3
Introduction
• L’allocation dynamique permet précisément de résoudre ce type
de problème et autres.
• Son rôle est de réserver une zone de mémoire dont la taille peut
être spécifiée à l’exécution (et non pas à la compilation).
• Les fonctions de gestion dynamique de la mémoire permettent
d’allouer et de libérer de la mémoire pendant l’exécution.
• Mais avant d’en parler, on doit savoir quelles sont les zones de
mémoire associées à un programme C ?
4
Les zones de mémoire d’un programme
Il existe 4 zones de mémoire où le Mémoire centrale
compilateur range un programme et ses
données :
• Une zone1 : la pile (stack).
• Une zone2 : le tas (heap).
• Une zone3 : mémoire statique
(segment).
• Une zone4 : mémoire programme
(code)
5
Les zones de mémoire d’un programme
• zone « Code » :
Contient le code binaire de notre programme compilé.
• zone « statique » :
Contient les données globales du programme (celles qui sont
en vie durant tout le temps d'exécution du programme)
• zone « tas » :
Utilisée pour stocker les données allouées dynamiquement.
• zone « pile » :
Contient des variables locales, gère le passage de paramètres
et de contextes lors d'appels de fonctions, gère la récursivité, 6
etc.
Types de variables
• Variables automatiques : créées en pile et détruites au fur et à
mesure de l'exécution des fonctions où elles sont définies.
§ Exemple : variables locales « normales ».
• Variables statiques : créées dans le segment de données (zone
statique), occupant un espace mémoire déterminé à la
compilation, elles persistent jusqu’à l’arrêt du programme.
§ Exemple : variables locales déclarées avec le mot clé static et
variables globales.
• Variables dynamiques : dont la création et la destruction
dépendent des demandes explicites faites par le programme au
cours de l'exécution dans le tas (heap). Elles persistent dans la
7
mémoire jusqu’à libération.
Allocation statique
• L'allocation statique s'occupe des données à taille fixe dans le
programme.
• Le compilateur calcule la taille globale de la mémoire requise
pour ces données avant l’exécution du programme.
• Les zones concernées par l’allocation statique sont :
• la pile pour les déclarations locales et les paramètres des
fonctions,
• la zone globale statique dans le cas des déclarations globales et
des déclarations avec le mot clé static.
8
Allocation des variables
Variables normales Variables pointeurs
• La réservation de la • Une variable pointeur est aussi
mémoire s'est déroulée déclaré statiquement.
automatiquement par • Mais, le nombre d'octets p à
l'emploi des déclarations réserver pour un pointeur
statiques des variables. dépend de la machine et du
• Le nombre d'octets à 'modèle' de mémoire choisi.
réserver pour une variable • Donc quelque soit le type de
se calcule pendant la pointeur déclaré, il y aura lieu
compilation à l’aide de son réservation de p octets en
type. mémoire.
9
Allocation dynamique
Un certain nombre de fonctions standards (stdlib.h) permet de
gérer dynamiquement la mémoire suivant deux opérations
différentes :
• L'allocation dynamique de mémoire : la plus importante
fonction de cette opération est malloc qui effectue une
allocation de base, les autres étant des variantes ou des
fonctions secondaires.
• La libération de la mémoire : réalisée par la fonction free qui
permettra de réutiliser la mémoire lors de l’exécution de
programme.
10
La fonction malloc
• Prototype de la fonction : void * malloc ( size_t t );
• Utilisation de la fonction :
type_var* ptr=NULL ;
ptr = (type_var*)malloc( nb_octets );
if (ptr==NULL) ...
Ou bien : type_var* ptr=NULL ;
ptr = (type_var *)malloc( sizeof(type_var) * nb_var );
if (ptr==NULL) ...
L'utilisation de l'opérateur sizeof est recommandée pour éviter le calcul « à
la main » de nb_octets.
Si une telle allocation est impossible par manque de place, «malloc» retourne
la valeur de pointeur « NULL ». Sinon elle retourne l'adresse du premier octet
de la zone allouée dans le tas. D’où vient l’intérêt du test if(ptr==NULL) …
Toutes les valeurs retournées par la fonction « malloc » sont de type « void * ». 11
Pour que le compilateur puisse considérer autre chose qu'une zone d'octets,
il faut effectuer une conversion explicite de type (un cast).
Exemple
int * tab;
/* Création d'un tableau de 4 entiers */
tab = (int*) malloc ( 4 * sizeof(int) );
if(tab == NULL) {
printf("Erreur d’allocation de mémoire") ;
exit(1);}
tab[0] = 1;
tab[1] = 2;
tab[2] = 3;
tab[3] = 4;
for ( i = 0 ; i < 4 ; i++ ) { Résultat:
printf(" tab[%d] = %d \n", i , tab[i] ); tab[0] = 1
} tab[1] = 2
tab[2] = 3 12
tab[3] = 4
La fonction calloc
• Prototype de la fonction:
void * calloc (size_t n, size_t size);
• Le premier argument (size_t n) est le nombre d'éléments qu'on
souhaite pouvoir stocker en mémoire,
• et le deuxième (size_t size) est la taille de ces éléments que l'on
obtient avec l'opérateur sizeof().
int *ptr = (int*) calloc(20 ,sizeof (int));
Remarque: Il existe deux différences majeures entre malloc et calloc :
• Premièrement en nombre d’arguments : malloc() prend un seul
argument, alors que calloc() prend deux arguments.
• Deuxièmement, malloc() n’initialise pas la mémoire allouée, alors 13
que calloc() initialise la mémoire allouée à ZERO.
La fonction realloc
• Prototype de la fonction: void* realloc(void *ptr, size_t size);
• Le premier argument est le pointeur sur lequel on désire effectuer
l'opération,
• le deuxième argument est la taille de l'espace mémoire qu'on veut
réallouer.
• La fonction realloc() s'utilise après qu'on ait utilisé malloc() ou
calloc()
• On peut aussi la rappeler plusieurs fois de suite (dans une boucle for
ou while par exemple).
• Elle sert à ré-attribuer de la mémoire à un pointeur mais pour une
taille mémoire différente.
• La fonction réalloue le bloc mémoire tout en gardant le contenu de
ce qui se trouvait dans le bloc précédent.
14
Exemple
int * tab;
/* Création d'un tableau de 3 entiers */
tab = (int*) calloc ( 3 , sizeof(int) );
if(tab == NULL) {printf("Erreur d’allocation de mémoire") ;exit(1);}
tab[0] = 1;
tab[1] = 2;
tab[2] = 3;
/* Ajout d'un element au tableau */
tab= (int*) realloc (tab, 4 * sizeof(int) );
if(tab == NULL) {printf("Erreur d’allocation de mémoire") ;exit(1);}
tab[3] = 4;
for ( i = 0 ; i < 4 ; i++ ) {
printf(" tab[%d] = %d \n", i , tab[i] ); Résultat:
} tab[0] = 1
tab[1] = 2
tab[2] = 3 15
tab[3] = 4
La fonction free
• Un des intérêts de l'allocation dynamique est de libérer l'espace
mémoire alloué lorsqu'il n'est plus nécessaire.
• Cet espace pourra donc être réaffecté ultérieurement lors de
nouvelles demandes dynamiques.
• La libération de la mémoire allouée dynamiquement s'effectue
très simplement avec la fonction free
• Le prototype de la fonction: void free(void * bloc)
• Utilisation de la fonction :
free( ptr ) ;
ptr=NULL;
16
Tableau dynamique
• La manipulation de tableaux, et non de pointeurs, possède
certains inconvénients dûs au fait qu'un tableau est un pointeur
constant.
• Ainsi:
• on ne peut pas créer de tableaux dont la taille est une variable du
programme,
• on ne peut pas créer de tableaux bidimensionnels dont les lignes
n'ont pas toutes le même nombre d'éléments.
• Ces opérations deviennent possibles dès que l'on manipule des
pointeurs alloués dynamiquement.
17
Tableau dynamique
• Cas de tableau à une dimension:
• Pour créer un tableau d'entiers à n éléments où n est une
variable du programme, on écrit
int n;
int *tab;
...
tab = (int*)malloc(n * sizeof(int));
...
free(tab);
• Si on veut en plus que tous les éléments du tableau tab soient
initialisés à zéro, on remplace l'allocation dynamique
avec malloc par 18
tab= (int*)calloc(n , sizeof(int));
Tableau dynamique
• Cas de tableau à plusieurs dimensions:
• Un tableau à deux dimensions est, par définition, un tableau de
tableaux. Il s'agit donc en fait d'un pointeur vers un pointeur.
• Exactement comme pour les tableaux à une dimension, les pointeurs
de pointeurs ont de nombreux avantages sur les tableaux multi-
dimensionnés.
• On déclare un pointeur qui pointe sur un objet de type type* (deux
dimensions) de la même manière qu'un pointeur, c'est-à-dire
type **nom-du-pointeur;
• De même un pointeur qui pointe sur un objet de
type type ** (équivalent à un tableau à 3 dimensions) se déclare par
type ***nom-du-pointeur;
19
Tableau dynamique
• Cas de tableau à plusieurs dimensions:
• Par exemple, pour créer avec un pointeur de pointeur une matrice
à k lignes et n colonnes à coefficients entiers, on écrit :
int k, n; int **tab;
tab = (int**)malloc(k * sizeof(int*));
for (i = 0; i < k; i++)
tab[i] = (int*)malloc(n * sizeof(int));
....
for (i = 0; i < k; i++)
free(tab[i]);
free(tab);
• La première allocation dynamique réserve pour l'objet pointé
par tab l'espace-mémoire correspondant à k pointeurs sur des entiers.
• Ces k pointeurs correspondent aux lignes de la matrice. 20
• Les allocations dynamiques suivantes réservent pour chaque
pointeur tab[i] l'espace-mémoire nécessaire pour stocker n entiers.
Allocation dynamique et structures
• Cas de champ dynamique dans une structure:
• Un champ dynamique d’une structure est un champ déclaré sous forme
d’un pointeur sur un type, et doit être alloué dynamiquement avant
utilisation.
typedef struct {float r, i; } complexe;
typedef struct {
//pointeur sur tableau de complexes
complexe * tab ;
int taille ; // nombre d’éléments du tableau
} listeComplexes;
• Nous avons construire un autre type de structure nommé
listComplexes représentant un ensemble de complexes qui connait sa
taille.
21
• Notre tableau de complexes sera déclaré comme pointeur dynamique.
Allocation dynamique et structures
• Cas de champ dynamique dans une structure:
int longueur,i;
printf("La longueur de tableau :");
scanf("%d",&longueur);
listeComplexes t={NULL,longueur}; //déclaration et initialisation
//allocation dynamique et initialisation
[Link]=(complexe*) malloc (longueur * sizeof(complexe));
if([Link]==NULL) {printf("Erreur d’allocation de mémoire") ;exit(1);}
for(i=0;i<longueur;i++) {//initialisation par 0
[Link][i].r=0; [Link][i].i=0;
}//pour éviter cette boucle d’initialisation on peut utiliser calloc à l’allocation.
for(i=0;i<longueur;i++) {//affichage et libération de la mémoire
printf("t[%d]=%.2f + i%.2f \n",i,[Link][i].r,[Link][i].i);
}
free([Link]); 22
[Link]=NULL;
Allocation dynamique et structures
• Cas de structure dynamique :
• Pour illustrer le cas d’une structure allouée dynamiquement, on
utilisera une structure qui enregistre les informations d’un étudiant
(nom, taille et âge) déclarées comme variables normales.
typedef struct {
char nom[20];
float taille ;
short int age ;} etudiant;
• Donc, il est possible d’allouer dynamiquement un emplacement pour
un seul étudiant :
//Déclaration d’un pointeur pour la variable de structure.
etudiant * pEtudiant =NULL;
//Allocation dynamique
pEtudiant = (etudiant*) malloc( sizeof(etudiant) );
23
Remarque : on pourra faire les 2 instructions en une seul
(déclaration+allocation).
Allocation dynamique et structures
• Cas de structure dynamique :
• On peut aussi allouer dynamiquement une liste (tableau) d’étudiants
de la manière suivante :
//Déclaration d’un pointeur de type etudiant
etudiant * listeEtudiants=NULL;
//Allocation d'un tableau de structures
listeEtudiants=(etudiant*)malloc(nbETU*sizeof(etudiant) );
Remarque : on devra savoir le nombre d’étudiants (nbETU) avant
l’allocation.
Dans certain cas on trouve une allocation mixte (champs de structure
ainsi que la variable structurée, les deux, à allouer dynamiquement).Par
exemple: Structures auto-référencées.
24
Fonction d’allocation dynamique
• Cas de fonction avec valeur de retour:
Définition d’une fonction d’allocation :
double* fonctionAllocation(int nbElem){
double* ptr =NULL;
ptr= (double*)malloc( nbElem* sizeof(double) );
return ptr;
} //ici sizeof(*ptr) ~ sizeof(double)
Appel de la fonction d’allocation dans le programme :
int main(){
double* ptr =fonctionAllocation(5);
if(ptr == NULL)
{printf("Erreur d’allocation de mémoire") ;exit(1);}
//traitement …… 25
return 0;
}
Fonction d’allocation dynamique
• Cas de fonction sans valeur de retour:
Définition d’une fonction d’allocation :
void fonctionAllocation(double **ptr , int nombreElements){
*ptr =(double*) malloc( nombreElements * sizeof(**ptr) );
}// ici sizeof(**ptr) ~ sizeof(double)
Appel de la fonction d’allocation dans le programme :
int main(){
double * ptr=NULL;
fonctionAllocation(&ptr , 5);
if(ptr == NULL) { printf("Erreur …..") ;exit(1);}
//traitements........
return 0; 26
}