Inspirez un grand coup : ce chapitre est le dernier chapitre "théorique" que vous lirez avant un bon moment
De quoi va-t-on parler aujourd'hui ? On va voir comment créer une variable manuellement (= dynamiquement).
Quand on déclare une variable, on dit qu'on demande à allouer de la mémoire :
Code : C1int monNombre = 0;
Lorsque le programme arrive à une ligne comme celle-là, il se passe en fait les choses suivantes :
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 parfois on est obligés de faire comme ça.
Dans ce chapitre, nous allons :
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 sur les pointeurs avant de commencer quoi que ce soit !
Sommaire du chapitre :
Vous avez dit "programmer" ? Ayez les bons outils ! Votre premier programme Un monde de variables Une bête de calcul Les conditions Les boucles TP : Plus ou Moins, votre premier jeu Les fonctions La programmation modulaire A l'assaut des pointeurs Les tableaux Les chaînes de caractères Le préprocesseur Créez vos propres types de variables ! Lire et écrire dans des fichiers L'allocation dynamique TP : Réalisation d'un pendu Installation de la SDL Création d'une fenêtre et de surfaces Afficher des images La gestion des évènements (Partie 1/2) La gestion des évènements (Partie 2/2) TP : Mario Sokoban Maîtrisez le temps ! Ecrire du texte avec SDL_ttf Jouer du son avec FMOD TP : visualisation spectrale du son Créer une installation Créer une icône pour son programme La saisie de texte sécurisée
Ce lien n'est pas visible, veuillez vous connecter pour l'afficher. Je m'inscris! Ce lien n'est pas visible, veuillez vous connecter pour l'afficher. Je m'inscris! Ce lien n'est pas visible, veuillez vous connecter pour l'afficher. Je m'inscris!
La taille des variables
Selon le type de variable que vous demandez à 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 (un char), on n'a besoin que d'un octet en mémoire (c'est tout petit
).
En revanche, un int occupe généralement 4 octets en mémoire. Quant au double, il occupe 8 octets.
Le problème est... que ce n'est pas toujours le cas. Cela dépend de votre ordinateur : peut-être que chez vous un int occupe 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érateur sizeof().
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'un int, on devra donc écrire :
Code : C1sizeof(int)
A la compilation, cela sera remplacé par un nombre : le nombre d'octets que prend int en mémoire. Chez moi, sizeof(int) vaut 4, ce qui signifie que int occupe 4 octets. Chez vous, c'est probablement la même valeur, mais ce n'est pas une règle. Testez, vous verrez
Vous pouvez faire des printf pour afficher cela :
Code : C1
2
3
4printf("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 :
Code : Console
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ême la taille des autres types
Vous remarquerez que long et int occupent la même place en mémoire. Créer un long revient donc ici exactement à créer un int, cela prend 4 octets dans la mémoire.
En fait, le type "long" est équivalent à un type appelé "long int", qui est ici équivalent au type... "int". Bref, ça fait beaucoup de noms différents pour pas grand-chose au final
Avoir de nombreux types différents était utile à une époque où on n'avait pas beaucoup de mémoire. On cherchait à utiliser le minimum de mémoire possible en utilisant le type le plus adapté.
Aujourd'hui, cela ne sert plus vraiment car la mémoire d'un ordinateur est très grande.
Peut-on afficher la taille d'un type personnalisé qu'on a créé (une structure) ?
Oui ! sizeof marche aussi sur les structures !
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13typedef struct Coordonnees Coordonnees;
struct Coordonnees
{
int x;
int y;
};
int main(int argc, char *argv[])
{
printf("Coordonnees : %d octets\n", sizeof(Coordonnees));
return 0;
}
Code : Console
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 précis et corrects maintenant qu'on connaît la taille de chacun des types de variables (c'est pas trop tôt
)
Si on déclare une variable de type int :
Code : C1int nombre = 18;
... et que sizeof(int) indique 4 octets sur notre ordinateur, alors la variable occupera 4 octets en mémoire !
Supposons que la variable nombre soit allouée à l'adresse 1600 en mémoire. On aurait alors le schéma suivant :
Ici, on voit bien que notre variable "nombre" de type int qui 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 un char, alors on n'aurait occupé qu'un seul octet en mémoire :
Imaginez maintenant un tableau de int !
Chaque "case" du tableau occupera 4 octets. Si notre tableau fait 100 cases :
Code : C1int tableau[100];
Alors on occupera 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 type "Coordonnees" :
Code : C1Coordonnees tableau[100];
... on utilisera (allez c'est facile
) : 8 * 100 = 800 octets en mémoire.
Il est important de bien comprendre ces petits calculs pour la suite du chapitre.
C'est de la multiplication de niveau Primaire ça
Allocation de mémoire dynamique
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 :
Quand vous faites une allocation manuelle de mémoire (ce qu'on va apprendre à faire maintenant), vous devez toujours suivre ces 3 étapes :
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 : C1void* 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
3int* 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 :
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
14int 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 : C1void 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
16int 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
19int 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
11int 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 :
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
Allocation dynamique d'un tableau
Pour le moment, on s'est servis de 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 ça
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 se sert de l'allocation dynamique 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 de int pour stocker les âges, comme ceci :
Code : C1int ageAmis[15];
Mais qui vous dit que l'utilisateur a 15 amis ? Peut-être qu'il en a plus que ça !
Lorsque vous rédigez 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 15 int, s'il en a 28 on créera un tableau de 28 int 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 :
Code : C1int amis[nombreDAmis];
(Notez : ce code marche peut-être sur certains compilateurs mais uniquement dans des cas précis, il est recommandé de ne pas l'utiliser !)
L'avantage de l'allocation dynamique, c'est qu'elle nous permet de créer un tableau qui a exactement la taille de la variable nombreDAmis, et cela grâce à un code qui marchera partout !
On va demander au malloc de nous réserver nombreDAmis * sizeof(int) octets en mémoire :
Code : C1amis = malloc(nombreDAmis * sizeof(int));
Ce code permet de créer un tableau de type int qui a une taille correspondant exactement au nombre de ses amis !
Voici ce que va faire le programme dans l'ordre :
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37int 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 pas
{
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);
}
// 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);
}
// On libère la mémoire allouée avec malloc, on n'en a plus besoin
free(ageAmis);
}
return 0;
}
Code : Console
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 le malloc
).
Que je vous rassure, dans la suite du cours nous aurons l'occasion d'utiliser le malloc pour des choses plus intéressantes que le stockage de l'âge de ses amis
SOURCE : siteduzero.com
De quoi va-t-on parler aujourd'hui ? On va voir comment créer une variable manuellement (= dynamiquement).
Quand on déclare une variable, on dit qu'on demande à allouer de la mémoire :
Code : C1int monNombre = 0;
Lorsque le programme arrive à une ligne comme celle-là, il se passe en fait les choses suivantes :
- Votre programme demande au système d'exploitation (Windows, Linux, Mac OS...) la permission d'utiliser un peu de mémoire.
- 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).
- 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" (Nota : l'histoire ne précise pas si le programme dit "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
Dans ce chapitre, nous allons :
- Etudier le fonctionnement de la mémoire (oui, encore !
- Puis, nous attaquerons le gros du sujet : 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, nous verrons 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.
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 sur les pointeurs avant de commencer quoi que ce soit !
Sommaire du chapitre :
- Ce lien n'est pas visible, veuillez vous connecter pour l'afficher. Je m'inscris!
- Ce lien n'est pas visible, veuillez vous connecter pour l'afficher. Je m'inscris!
- Ce lien n'est pas visible, veuillez vous connecter pour l'afficher. Je m'inscris!
- Ce lien n'est pas visible, veuillez vous connecter pour l'afficher. Je m'inscris!
Vous avez dit "programmer" ? Ayez les bons outils ! Votre premier programme Un monde de variables Une bête de calcul Les conditions Les boucles TP : Plus ou Moins, votre premier jeu Les fonctions La programmation modulaire A l'assaut des pointeurs Les tableaux Les chaînes de caractères Le préprocesseur Créez vos propres types de variables ! Lire et écrire dans des fichiers L'allocation dynamique TP : Réalisation d'un pendu Installation de la SDL Création d'une fenêtre et de surfaces Afficher des images La gestion des évènements (Partie 1/2) La gestion des évènements (Partie 2/2) TP : Mario Sokoban Maîtrisez le temps ! Ecrire du texte avec SDL_ttf Jouer du son avec FMOD TP : visualisation spectrale du son Créer une installation Créer une icône pour son programme La saisie de texte sécurisée
Ce lien n'est pas visible, veuillez vous connecter pour l'afficher. Je m'inscris! Ce lien n'est pas visible, veuillez vous connecter pour l'afficher. Je m'inscris! Ce lien n'est pas visible, veuillez vous connecter pour l'afficher. Je m'inscris!
La taille des variables
Selon le type de variable que vous demandez à 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 (un char), on n'a besoin que d'un octet en mémoire (c'est tout petit
En revanche, un int occupe généralement 4 octets en mémoire. Quant au double, il occupe 8 octets.
Le problème est... que ce n'est pas toujours le cas. Cela dépend de votre ordinateur : peut-être que chez vous un int occupe 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érateur sizeof().
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'un int, on devra donc écrire :
Code : C1sizeof(int)
A la compilation, cela sera remplacé par un nombre : le nombre d'octets que prend int en mémoire. Chez moi, sizeof(int) vaut 4, ce qui signifie que int occupe 4 octets. Chez vous, c'est probablement la même valeur, mais ce n'est pas une règle. Testez, vous verrez
Vous pouvez faire des printf pour afficher cela :
Code : C1
2
3
4printf("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 :
Code : Console
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ême la taille des autres types
Vous remarquerez que long et int occupent la même place en mémoire. Créer un long revient donc ici exactement à créer un int, cela prend 4 octets dans la mémoire.
En fait, le type "long" est équivalent à un type appelé "long int", qui est ici équivalent au type... "int". Bref, ça fait beaucoup de noms différents pour pas grand-chose au final
Aujourd'hui, cela ne sert plus vraiment car la mémoire d'un ordinateur est très grande.
Peut-on afficher la taille d'un type personnalisé qu'on a créé (une structure) ?
Oui ! sizeof marche aussi sur les structures !
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13typedef struct Coordonnees Coordonnees;
struct Coordonnees
{
int x;
int y;
};
int main(int argc, char *argv[])
{
printf("Coordonnees : %d octets\n", sizeof(Coordonnees));
return 0;
}
Code : Console
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 précis et corrects maintenant qu'on connaît la taille de chacun des types de variables (c'est pas trop tôt
Si on déclare une variable de type int :
Code : C1int nombre = 18;
... et que sizeof(int) indique 4 octets sur notre ordinateur, alors la variable occupera 4 octets en mémoire !
Supposons que la variable nombre soit allouée à l'adresse 1600 en mémoire. On aurait alors le schéma suivant :
Ici, on voit bien que notre variable "nombre" de type int qui 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 un char, alors on n'aurait occupé qu'un seul octet en mémoire :
Imaginez maintenant un tableau de int !
Chaque "case" du tableau occupera 4 octets. Si notre tableau fait 100 cases :
Code : C1int tableau[100];
Alors on occupera 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 type "Coordonnees" :
Code : C1Coordonnees tableau[100];
... on utilisera (allez c'est facile
Il est important de bien comprendre ces petits calculs pour la suite du chapitre.
C'est de la multiplication de niveau Primaire ça
Allocation de mémoire dynamique
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 : C1void* 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
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
3int* 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
14int 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 : C1void 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
16int 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
19int 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
11int 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
Allocation dynamique d'un tableau
Pour le moment, on s'est servis de 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 ça
Quand a-t-on besoin de l'allocation dynamique me direz-vous ?
Le plus souvent, on se sert de l'allocation dynamique 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 de int pour stocker les âges, comme ceci :
Code : C1int ageAmis[15];
Mais qui vous dit que l'utilisateur a 15 amis ? Peut-être qu'il en a plus que ça !
Lorsque vous rédigez 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
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 :
Code : C1int amis[nombreDAmis];
(Notez : ce code marche peut-être sur certains compilateurs mais uniquement dans des cas précis, il est recommandé de ne pas l'utiliser !)
L'avantage de l'allocation dynamique, c'est qu'elle nous permet de créer un tableau qui a exactement la taille de la variable nombreDAmis, et cela grâce à un code qui marchera partout !
On va demander au malloc de nous réserver nombreDAmis * sizeof(int) octets en mémoire :
Code : C1amis = malloc(nombreDAmis * sizeof(int));
Ce code permet de créer un tableau de type int qui a une taille correspondant exactement au nombre de ses amis !
Voici ce que va faire le programme dans l'ordre :
- On demande à l'utilisateur combien il a d'amis
- On crée un tableau de int faisant une taille égale à son nombre d'amis (via malloc)
- On demande l'âge de chacun de ses amis un à un, qu'on stocke dans le tableau
- On affiche l'âge des amis pour montrer qu'on a bien mémorisé tout cela
- A la fin, on n'a plus besoin du tableau contenant l'âge des amis : on le libère avec la fonction free.
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37int 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 pas
{
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);
}
// 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);
}
// On libère la mémoire allouée avec malloc, on n'en a plus besoin
free(ageAmis);
}
return 0;
}
Code : Console
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 le malloc
Que je vous rassure, dans la suite du cours nous aurons l'occasion d'utiliser le malloc pour des choses plus intéressantes que le stockage de l'âge de ses amis
SOURCE : siteduzero.com