Rentrons maintenant dans le vif du sujet.
Le but du chapitre, c'était quoi justement ?
Ah oui : 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 2 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 (ce qu'on va apprendre à faire maintenant), vous devez toujours suivre ces 3 étapes :
- Appeler malloc pour demander de la mémoire
- Vérifier la valeur retournée par malloc pour savoir si l'OS a bien réussi à allouer la mémoire.
- Une fois qu'on a fini d'utiliser la mémoire, on doit la libérer avec free. 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.
Tiens, ces 3 étapes ça vous rappelle pas le chapitre sur les fichiers ça ?

Ben moi si
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 libère quand on a fini d'utiliser.
Nous allons maintenant étudier la fonction malloc.
malloc : demande d'allocation de mémoire
Le prototype de la fonction malloc est assez comique vous allez voir :
Code : C1 | void* malloc(size_t nombreOctetsNecessaires);
|
La fonction prend un paramètre : le nombre d'octets à réserver. Ainsi, il suffira d'écrire sizeof(int) dans ce paramètre pour réserver suffisamment d'espace pour stocker un int.
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 que "void" signifiait "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écidemment un sens de l'humour très développé
Ca te dérangerait pas trop de nous donner quelques explications ?
Oui oui j'y viens

Je me rappelle juste la première fois que j'ai vu le prototype de malloc, je suis resté la bouche ouverte un petit moment devant mon écran avant de comprendre
En fait, cette fonction renvoie un pointeur indiquant l'adresse que l'OS a réservé 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 fonction malloc ne 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 un int qu'un long par exemple !
Comme malloc ne sait pas quel type elle doit retourner, elle renvoie le type void*.
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 (
hahem) à créer manuellement une variable de type int en mémoire, je devrai indiquer à malloc que j'ai besoin de sizeof(int) octets en mémoire.
Je récupère le résultat du malloc dans un pointeur sur int.
Code : C1
2
3 | 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é reservee.
|
A la fin de ce code, memoireAllouee est 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 de tout à l'heure).
Tester le pointeur
La fonction malloc a donc renvoyé dans notre pointeur memoireAllouee l'adresse qui a été réservée pour vous en mémoire.
2 possibilités :
- Si l'allocation a marché, notre pointeur contient une adresse.
- Si l'allocation a échoué, notre pointeur contient l'adresse NULL.
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
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 qu'il ne pourra pas continuer convenablement de toute manière.
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 (ça correspond au return du main()).
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14 | 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 de NULL, 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 fonction fclose pour fermer un fichier dont on n'avait plus besoin, on va utiliser la fonction free pour libérer la mémoire quand on n'en a plus besoin.
Code : C1 | void free(void* pointeur);
|
La fonction free a juste besoin de l'adresse mémoire à libérer. On va donc lui envoyer notre pointeur, c'est-à-dire memoireAllouee dans 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 :
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | 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 faire quelque chose qu'on a appris à faire il y a longtemps : on va demander l'âge de l'utilisateur et on va 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) au lieu d'automatiquement comme auparavant. Alors oui, du coup, le code est un peu plus compliqué. Mais faites l'effort de bien essayer de le comprendre, c'est important :
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | 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;
}
|
Code : Console | Quel age avez-vous ? 31
Vous avez 31 ans |
Attention : comme memoireAllouee est un pointeur, on ne l'utilise pas de la même manière qu'une vraie variable. Pour obtenir la valeur de la variable, il faut mettre une étoile devant : "*memoireAllouee" (regardez le printf). Tandis que pour indiquer l'adresse, on a juste besoin d'écrire le nom du pointeur "memoireAllouee" (regardez le scanf)
Tout cela a été expliqué dans le chapitre sur les pointeurs. Toutefois, on met généralement du temps à s'y faire, et il est probable que vous confondiez encore. Si c'est votre cas, vous DEVEZ relire le chapitre sur les pointeurs, qui est fondamental.
Revenons à notre code. On y a alloué dynamiquement une variable de type int.
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 :
Code : C 1
2
3
4
5
6
7
8
9
10
11 | 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)
|
Code : Console | Quel age avez-vous ? 31
Vous avez 31 ans |
En résumé : il y a 2 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 ! On est parfois obligé d'allouer manuellement de la mémoire, comme on va le voir maintenant
