Fil d'Ariane
Mis à jour le mercredi 21 juin 2017
  • 40 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

Ce cours existe en livre papier.

Ce cours existe en eBook.

Vous pouvez obtenir un certificat de réussite à l'issue de ce cours.

Vous pouvez être accompagné et mentoré par un professeur particulier par visioconférence sur ce cours.

J'ai tout compris !

L'allocation dynamique

Connectez-vous ou inscrivez-vous pour bénéficier de toutes les fonctionnalités de ce cours !

Toutes les variables que nous avons créées jusqu'ici étaient construites automatiquement par le compilateur du langage C. C'était la méthode simple. Il existe cependant une façon plus manuelle de créer des variables que l'on appelle l'allocation dynamique.

Un des principaux intérêts de l'allocation dynamique est de permettre à un programme de réserver la place nécessaire au stockage d'un tableau en mémoire dont il ne connaissait pas la taille avant la compilation. En effet, jusqu'ici, la taille de nos tableaux était fixée « en dur » dans le code source. Après lecture de ce chapitre, vous allez pouvoir créer des tableaux de façon bien plus flexible !

Il est impératif de bien savoir manipuler les pointeurs pour pouvoir lire ce chapitre ! Si vous avez encore des doutes sur les pointeurs, je vous recommande d'aller relire le chapitre correspondant avant de commencer.

Quand on déclare une variable, on dit qu'on demande à allouer de la mémoire :

int monNombre = 0;

Lorsque le programme arrive à une ligne comme celle-ci, il se passe en fait les choses suivantes :

  1. votre programme demande au système d'exploitation (Windows, Linux, Mac OS…) la permission d'utiliser un peu de mémoire ;

  2. le système d'exploitation répond à votre programme en lui indiquant où il peut stocker cette variable (il lui donne l'adresse qu'il lui a réservée) ;

  3. lorsque la fonction est terminée, la variable est automatiquement supprimée de la mémoire. Votre programme dit au système d'exploitation : « Je n'ai plus besoin de l'espace en mémoire que tu m'avais réservé à telle adresse, merci ! L'histoire ne précise pas si le programme dit vraiment « merci » à l'OS, mais c'est tout dans son intérêt parce que c'est l'OS qui contrôle la mémoire !

Jusqu'ici, les choses étaient automatiques. Lorsqu'on déclarait une variable, le système d'exploitation était automatiquement appelé par le programme.
Que diriez-vous de faire cela manuellement ? Non pas par pur plaisir de faire quelque chose de compliqué (même si c'est tentant !), mais plutôt parce que nous allons parfois être obligés de procéder comme cela.

Dans ce chapitre, nous allons :

  • étudier le fonctionnement de la mémoire (oui, encore !) pour découvrir la taille que prend une variable en fonction de son type ;

  • puis attaquer le sujet lui-même : nous verrons comment demander manuellement de la mémoire au système d'exploitation. On fera ce qu'on appelle de l'allocation dynamique de mémoire ;

  • enfin, découvrir l'intérêt de faire une allocation dynamique de mémoire en apprenant à créer un tableau dont la taille n'est connue qu'à l'exécution du programme.

La taille des variables

Selon le type de variable que vous demandez de créer (char,int,double,float…), vous avez besoin de plus ou moins de mémoire.

En effet, pour stocker un nombre compris entre -128 et 127 (unchar), on n'a besoin que d'un octet en mémoire. C'est tout petit.
En revanche, unintoccupe généralement 4 octets en mémoire. Quant audouble, il occupe 8 octets.

Le problème est… que ce n'est pas toujours le cas. Cela dépend des machines : peut-être que chez vous unintoccupe 8 octets, qui sait ?
Notre objectif ici est de vérifier quelle taille occupe chacun des types sur votre ordinateur.

Il y a un moyen très facile pour savoir cela : utiliser l'opérateursizeof().
Contrairement aux apparences, ce n'est pas une fonction mais une fonctionnalité de base du langage C. Vous devez juste indiquer entre parenthèses le type que vous voulez analyser.
Pour connaître la taille d'unint, on devra donc écrire :

sizeof(int)

À la compilation, cela sera remplacé par un nombre : le nombre d'octets que prendinten mémoire. Chez moi,sizeof(int)vaut 4, ce qui signifie queintoccupe 4 octets. Chez vous, c'est probablement la même valeur, mais ce n'est pas une règle. Testez pour voir, en affichant la valeur à l'aide d'unprintfpar exemple :

printf("char : %d octets\n", sizeof(char));
printf("int : %d octets\n", sizeof(int));
printf("long : %d octets\n", sizeof(long));
printf("double : %d octets\n", sizeof(double));

Chez moi, cela affiche :

char : 1 octets
int : 4 octets
long : 4 octets
double : 8 octets

Je n'ai pas mis tous les types que nous connaissons, je vous laisse le soin de tester vous-mêmes la taille des autres types.

Vous remarquerez quelongetintoccupent la même place en mémoire. Créer unlongrevient donc ici exactement à créer unint, cela prend 4 octets dans la mémoire.

Peut-on afficher la taille d'un type personnalisé qu'on a créé (une structure) ?

Oui !sizeofmarche aussi sur les structures !

typedef struct Coordonnees Coordonnees;
struct Coordonnees
{
    int x;
    int y;
};

int main(int argc, char *argv[])
{
    printf("Coordonnees : %d octets\n", sizeof(Coordonnees));

    return 0;
}
Coordonnees : 8 octets

Plus une structure contient de sous-variables, plus elle prend de mémoire. Terriblement logique, n'est-ce pas ?

Une nouvelle façon de voir la mémoire

Jusqu'ici, mes schémas de mémoire étaient encore assez imprécis. On va enfin pouvoir les rendre vraiment précis et corrects maintenant qu'on connaît la taille de chacun des types de variables.

Si on déclare une variable de typeint:

int nombre = 18;

… et quesizeof(int)indique 4 octets sur notre ordinateur, alors la variable occupera 4 octets en mémoire !

Supposons que la variablenombresoit allouée à l'adresse 1600 en mémoire. On aurait alors le schéma de la fig. suivante.

Un int occupant 4 octets en mémoire

Ici, on voit bien que notre variablenombrede typeintqui vaut 18 occupe 4 octets dans la mémoire.
Elle commence à l'adresse 1600 (c'est son adresse) et termine à l'adresse 1603. La prochaine variable ne pourra donc être stockée qu'à partir de l'adresse 1604 !

Si on avait fait la même chose avec unchar, on n'aurait alors occupé qu'un seul octet en mémoire (fig. suivante).

Un char occupant 1 octet en mémoire

Imaginez maintenant un tableau deint!
Chaque « case » du tableau occupera 4 octets. Si notre tableau fait 100 cases :

int tableau[100];

on occupera alors en réalité 4 * 100 = 400 octets en mémoire.

Même si le tableau est vide, il prend 400 octets ?

Bien sûr ! La place en mémoire est réservée, aucun autre programme n'a le droit d'y toucher (à part le vôtre). Une fois qu'une variable est déclarée, elle prend immédiatement de la place en mémoire.

Notez que si on crée un tableau de typeCoordonnees:

Coordonnees tableau[100];

… on utilisera cette fois : 8 * 100 = 800 octets en mémoire.

Il est important de bien comprendre ces petits calculs pour la suite du chapitre.

Allocation de mémoire dynamique

Entrons maintenant dans le vif du sujet. Je vous rappelle notre objectif : apprendre à demander de la mémoire manuellement.

On va avoir besoin d'inclure la bibliothèque<stdlib.h>. Si vous avez suivi mes conseils, vous devriez avoir inclus cette bibliothèque dans tous vos programmes, de toute façon. Cette bibliothèque contient deux fonctions dont nous allons avoir besoin :

  • malloc(« Memory ALLOCation », c'est-à-dire « Allocation de mémoire ») : demande au système d'exploitation la permission d'utiliser de la mémoire ;

  • free(« Libérer ») : permet d'indiquer à l'OS que l'on n'a plus besoin de la mémoire qu'on avait demandée. La place en mémoire est libérée, un autre programme peut maintenant s'en servir au besoin.

Quand vous faites une allocation manuelle de mémoire, vous devez toujours suivre ces trois étapes :

  1. appelermallocpour demander de la mémoire ;

  2. vérifier la valeur retournée parmallocpour savoir si l'OS a bien réussi à allouer la mémoire ;

  3. une fois qu'on a fini d'utiliser la mémoire, on doit la libérer avecfree. Si on ne le fait pas, on s'expose à des fuites de mémoire, c'est-à-dire que votre programme risque au final de prendre beaucoup de mémoire alors qu'il n'a en réalité plus besoin de tout cet espace.

Ces trois étapes vous rappellent-elles le chapitre sur les fichiers ? Elles devraient ! Le principe est exactement le même qu'avec les fichiers : on alloue, on vérifie si l'allocation a marché, on utilise la mémoire, puis on la libère quand on a fini de l'utiliser.

malloc: demande d'allocation de mémoire

Le prototype de la fonctionmallocest assez comique, vous allez voir :

void* malloc(size_t nombreOctetsNecessaires);

La fonction prend un paramètre : le nombre d'octets à réserver. Ainsi, il suffira d'écriresizeof(int)dans ce paramètre pour réserver suffisamment d'espace pour stocker unint.

Mais c'est surtout ce que la fonction renvoie qui est curieux : elle renvoie un…void*! Si vous vous souvenez du chapitre sur les fonctions, je vous avais dit quevoidsignifiait « vide » et qu'on utilisait ce type pour indiquer que la fonction ne retournait aucune valeur.

Alors ici, on aurait une fonction qui retourne un… « pointeur sur vide » ? En voilà une bien bonne !
Ces programmeurs ont décidément un sens de l'humour très développé.

Rassurez-vous, il y a une raison. En fait, cette fonction renvoie un pointeur indiquant l'adresse que l'OS a réservée pour votre variable. Si l'OS a trouvé de la place pour vous à l'adresse 1600, la fonction renvoie donc un pointeur contenant l'adresse 1600.

Le problème, c'est que la fonctionmallocne sait pas quel type de variable vous cherchez à créer. En effet, vous ne lui donnez qu'un paramètre : le nombre d'octets en mémoire dont vous avez besoin. Si vous demandez 4 octets, ça pourrait aussi bien être unintqu'unlongpar exemple !

Commemallocne sait pas quel type elle doit retourner, elle renvoie le typevoid*. Ce sera un pointeur sur n'importe quel type. On peut dire que c'est un pointeur universel.

Passons à la pratique.
Si je veux m'amuser (hum !) à créer manuellement une variable de typeinten mémoire, je devrais indiquer àmallocque j'ai besoin desizeof(int)octets en mémoire.
Je récupère le résultat dumallocdans un pointeur surint.

int* memoireAllouee = NULL; // On crée un pointeur sur int

memoireAllouee = malloc(sizeof(int)); // La fonction malloc inscrit dans notre pointeur l'adresse qui a été reservée.

À la fin de ce code,memoireAlloueeest un pointeur contenant une adresse qui vous a été réservée par l'OS, par exemple l'adresse 1600 pour reprendre mes schémas précédents.

Tester le pointeur

La fonctionmalloca donc renvoyé dans notre pointeurmemoireAlloueel'adresse qui a été réservée pour vous en mémoire. Deux possibilités :

  • si l'allocation a marché, notre pointeur contient une adresse ;

  • si l'allocation a échoué, notre pointeur contient l'adresseNULL.

Il est peu probable qu'une allocation échoue, mais cela peut arriver. Imaginez que vous demandiez à utiliser 34 Go de mémoire vive, il y a très peu de chances que l'OS vous réponde favorablement.

Il est néanmoins recommandé de toujours tester si l'allocation a marché. On va faire ceci : si l'allocation a échoué, c'est qu'il n'y avait plus de mémoire de libre (c'est un cas critique). Dans un tel cas, le mieux est d'arrêter immédiatement le programme parce que, de toute manière, il ne pourra pas continuer convenablement.

On va utiliser une fonction standard qu'on n'avait pas encore vue jusqu'ici :exit(). Elle arrête immédiatement le programme. Elle prend un paramètre : la valeur que le programme doit retourner (cela correspond en fait aureturndumain()).

int main(int argc, char *argv[])
{
    int* memoireAllouee = NULL;

    memoireAllouee = malloc(sizeof(int));
    if (memoireAllouee == NULL) // Si l'allocation a échoué
    {
        exit(0); // On arrête immédiatement le programme
    }

    // On peut continuer le programme normalement sinon

    return 0;
}

Si le pointeur est différent deNULL, le programme peut continuer, sinon il faut afficher un message d'erreur ou même mettre fin au programme, parce qu'il ne pourra pas continuer correctement s'il n'y a plus de place en mémoire.

free: libérer de la mémoire

Tout comme on utilisait la fonctionfclosepour fermer un fichier dont on n'avait plus besoin, on va utiliser la fonctionfreepour libérer la mémoire dont on ne se sert plus.

void free(void* pointeur);

La fonctionfreea juste besoin de l'adresse mémoire à libérer. On va donc lui envoyer notre pointeur, c'est-à-dirememoireAlloueedans notre exemple.
Voici le schéma complet et final, ressemblant à s'y méprendre à ce qu'on a vu dans le chapitre sur les fichiers :

int main(int argc, char *argv[])
{
    int* memoireAllouee = NULL;

    memoireAllouee = malloc(sizeof(int));
    if (memoireAllouee == NULL) // On vérifie si la mémoire a été allouée
    {
        exit(0); // Erreur : on arrête tout !
    }

    // On peut utiliser ici la mémoire
    free(memoireAllouee); // On n'a plus besoin de la mémoire, on la libère

    return 0;
}

Exemple concret d'utilisation

On va programmer quelque chose qu'on a appris à faire il y a longtemps : demander l'âge de l'utilisateur et le lui afficher. La seule différence avec ce qu'on faisait avant, c'est qu'ici la variable va être allouée manuellement (on dit aussi dynamiquement) plutôt qu'automatiquement comme auparavant. Alors oui, du coup, le code est un peu plus compliqué. Mais faites l'effort de bien le comprendre, c'est important :

int main(int argc, char *argv[])
{
    int* memoireAllouee = NULL;

    memoireAllouee = malloc(sizeof(int)); // Allocation de la mémoire
    if (memoireAllouee == NULL)
    {
        exit(0);
    }

    // Utilisation de la mémoire
    printf("Quel age avez-vous ? ");
    scanf("%d", memoireAllouee);
    printf("Vous avez %d ans\n", *memoireAllouee);

    free(memoireAllouee); // Libération de mémoire

    return 0;
}
Quel age avez-vous ? 31
Vous avez 31 ans

Revenons à notre code. On y a alloué dynamiquement une variable de typeint.
Au final, ce qu'on a écrit revient exactement au même que d'utiliser la méthode « automatique » qu'on connaît bien maintenant :

int main(int argc, char *argv[])
{
    int maVariable = 0; // Allocation de la mémoire (automatique)

    // Utilisation de la mémoire
    printf("Quel age avez-vous ? ");
    scanf("%d", &maVariable);
    printf("Vous avez %d ans\n", maVariable);

    return 0;
} // Libération de la mémoire (automatique à la fin de la fonction)
Quel age avez-vous ? 31
Vous avez 31 ans

En résumé, il y a deux façons de créer une variable, c'est-à-dire d'allouer de la mémoire. Soit on le fait :

  • automatiquement : c'est la méthode que vous connaissez et qu'on a utilisée jusqu'ici ;

  • manuellement (dynamiquement) : c'est la méthode que je vous enseigne dans ce chapitre.

Je trouve la méthode dynamique compliquée et inutile !

Un peu plus compliquée… certes. Mais inutile, non ! Nous sommes parfois obligés d'allouer manuellement de la mémoire, comme nous allons le voir maintenant.

Allocation dynamique d'un tableau

Pour le moment, on a utilisé l'allocation dynamique uniquement pour créer une petite variable. Or en général, on ne se sert pas de l'allocation dynamique pour cela. On utilise la méthode automatique qui est plus simple.

Quand a-t-on besoin de l'allocation dynamique, me direz-vous ? Le plus souvent, on s'en sert pour créer un tableau dont on ne connaît pas la taille avant l'exécution du programme.

Imaginons par exemple un programme qui stocke l'âge de tous les amis de l'utilisateur dans un tableau. Vous pourriez créer un tableau deintpour stocker les âges, comme ceci :

int ageAmis[15];

Mais qui vous dit que l'utilisateur a 15 amis ? Peut-être qu'il en a plus que ça !
Lorsque vous écrivez le code source, vous ne connaissez pas la taille que vous devez donner à votre tableau. Vous ne le saurez qu'à l'exécution, lorsque vous demanderez à l'utilisateur combien il a d'amis.

L'intérêt de l'allocation dynamique est là : on va demander le nombre d'amis à l'utilisateur, puis on fera une allocation dynamique pour créer un tableau ayant exactement la taille nécessaire (ni trop petit, ni trop grand). Si l'utilisateur a 15 amis, on créera un tableau de 15int; s'il en a 28, on créera un tableau de 28int, etc.

Comme je vous l'ai appris, il est interdit en C de créer un tableau en indiquant sa taille à l'aide d'une variable :

int amis[nombreDAmis];

L'avantage de l'allocation dynamique, c'est qu'elle nous permet de créer un tableau qui a exactement la taille de la variablenombreDAmis, et cela grâce à un code qui fonctionnera partout !

On va demander aumallocde nous réservernombreDAmis * sizeof(int)octets en mémoire :

amis = malloc(nombreDAmis * sizeof(int));

Ce code permet de créer un tableau de typeintqui a une taille correspondant exactement au nombre d'amis !

Voici ce que fait le programme dans l'ordre :

  1. demander à l'utilisateur combien il a d'amis ;

  2. créer un tableau deintayant une taille égale à son nombre d'amis (viamalloc) ;

  3. demander l'âge de chacun de ses amis un à un, qu'on stocke dans le tableau ;

  4. afficher l'âge des amis pour montrer qu'on a bien mémorisé tout cela ;

  5. à la fin, puisqu'on n'a plus besoin du tableau contenant l'âge des amis, le libérer avec la fonctionfree.

int main(int argc, char *argv[])
{
    int nombreDAmis = 0, i = 0;
    int* ageAmis = NULL; // Ce pointeur va servir de tableau après l'appel du malloc

    // On demande le nombre d'amis à l'utilisateur
    printf("Combien d'amis avez-vous ? ");
    scanf("%d", &nombreDAmis);

    if (nombreDAmis > 0) // Il faut qu'il ait au moins un ami (je le plains un peu sinon :p)
    {
        ageAmis = malloc(nombreDAmis * sizeof(int)); // On alloue de la mémoire pour le tableau
        if (ageAmis == NULL) // On vérifie si l'allocation a marché ou non
        {
            exit(0); // On arrête tout
        }

        // On demande l'âge des amis un à un
        for (i = 0 ; i < nombreDAmis ; i++)
        {
            printf("Quel age a l'ami numero %d ? ", i + 1);
            scanf("%d", &ageAmis[i]);
        }

        // On affiche les âges stockés un à un
        printf("\n\nVos amis ont les ages suivants :\n");
        for (i = 0 ; i < nombreDAmis ; i++)
        {
            printf("%d ans\n", ageAmis[i]);
        }

        // On libère la mémoire allouée avec malloc, on n'en a plus besoin
        free(ageAmis);
    }

    return 0;
}
Combien d'amis avez-vous ? 5
Quel age a l'ami numero 1 ? 16
Quel age a l'ami numero 2 ? 18
Quel age a l'ami numero 3 ? 20
Quel age a l'ami numero 4 ? 26
Quel age a l'ami numero 5 ? 27

Vos amis ont les ages suivants :
16 ans
18 ans
20 ans
26 ans
27 ans

Ce programme est tout à fait inutile : il demande les âges et les affiche ensuite. J'ai choisi de faire cela car c'est un exemple « simple » (enfin, si vous avez compris lemalloc).

Je vous rassure : dans la suite du cours, nous aurons l'occasion d'utiliser lemallocpour des choses bien plus intéressantes que le stockage de l'âge de ses amis !

En résumé

  • Une variable occupe plus ou moins d'espace en mémoire en fonction de son type.

  • On peut connaître le nombre d'octets occupés par un type à l'aide de l'opérateursizeof().

  • L'allocation dynamique consiste à réserver manuellement de l'espace en mémoire pour une variable ou un tableau.

  • L'allocation est effectuée avecmalloc()et il ne faut surtout pas oublier de libérer la mémoire avecfree()dès qu'on n'en a plus besoin.

  • L'allocation dynamique permet notamment de créer un tableau dont la taille est déterminée par une variable au moment de l'exécution.

Exemple de certificat de réussite
Exemple de certificat de réussite