Bonjour et bienvenue. Maintenant que nous comprenons l'allocation de mémoire
dynamique, nous devons l'implémenter, nous devons réellement savoir comment le faire en code C.
Nous allons donc parler de trois fonctions vraiment
importantes, malloc, calloc, realloc.
Vous voyez qu'ils ont en quelque sorte des noms où l'allocation
de mémoire calloc et la réallocation.
Le moyen le plus simple et le plus courant d'allouer de la
mémoire consiste à utiliser la fonction de bibliothèque standard nommée malloc.
Cela alloue de la mémoire dynamique au moment de l'exécution.
Pour appeler cette fonction, vous devez inclure le fichier d'en-tête de la bibliothèque standard.
Vous spécifiez le nombre d'octets de mémoire que vous souhaitez allouer comme argument.
Ce qui est renvoyé par cette fonction est l'adresse du premier octet de mémoire alloué.
Parce que vous obtenez une adresse renvoyée, un pointeur est le seul endroit pour le placer.
C'est pourquoi l'allocation dynamique de mémoire n'est possible que via
ce concept de pointeur car il renvoie des adresses.
Vous ne pouvez mettre une adresse que dans un pointeur, c'est ce que stocke un pointeur, un pointeur stocke une adresse.
Alors, voici comment procéder avec une syntaxe très simple.
Habituellement, lorsque vous créez votre pointeur, vous n'êtes pas obligé de le faire.
Vous pouvez créer votre pointeur et le définir sur null.
Et puis dans la ligne suivante, vous pourriez malloc.
Mais voici la syntaxe standard que vous créez votre pointeur int, pointeur
vers un type int.
Et puis vous remarquerez que vous appelez malloc.
Et ici, nous disons malloc, nous allons créer 100 octets pour malloc,
puis nous allons le convertir en un pointeur int.
Étant donné que malloc ne renvoie qu'une adresse, vous devez la
convertir en pointeur de fin car cette adresse
stocke un pointeur int.
C'est la manière standard de le faire.
Vous demandez maintenant 100 octets de mémoire
et vous l'assignez à l'adresse du bloc de mémoire au nombre p.
pNumber va pointer vers le premier emplacement int au début des 100 octets alloués.
Donc, ce que cela signifie, c'est que les entiers font 4 octets par morceau,
cela peut contenir 25 valeurs int.
Et encore une fois, cela suppose que vous avez 4 octets.
Maintenant, selon le système que vous utilisez, ils peuvent être plus grands, peut-être plus petits.
Vous devriez donc voir le problème avec cela. Ce code ne fonctionnera
pas sur différents systèmes car vous allouez en fonction d'une hypothèse, basée
sur l'hypothèse de la taille int.
Il n'est donc pas vraiment bon d'utiliser malloc de
cette manière avec un nombre constant de 100.
Ce que vous voulez faire, c'est utiliser l'opérateur sizeof.
Il serait donc préférable de supprimer l'hypothèse selon laquelle les entiers sont de 4 octets.
Au lieu de cela, si vous souhaitez allouer de la place pour 25 entiers à l'intérieur
de l'argument malloc, spécifiez
le nombre d'entiers que vous souhaitez allouer, puis multipliez-le par la taille des entiers.
Et cela rendra le code beaucoup plus portable.
Ce code fonctionnera désormais sur différents systèmes, différents systèmes d'exploitation, différentes architectures.
Vous convertissez toujours la valeur de retour de l'adresse car elle doit pointer vers un
type int, donc vous dites int star. La seule différence ici est que vous utilisez l'opérateur sizeof.
L'opérateur Sizeof est vraiment important lorsque vous allouez de la mémoire en utilisant malloc ou
en utilisant calloc ou realloc.
Vous utilisez et voulez toujours utiliser l'opérateur sizeof pour
le type que vous allouez, puis multipliez cela par le nombre de
nombres particuliers que vous voulez, donc si vous voulez stocker
25 int, vous faites donc 25.
Ainsi, l'argument de malloc ci-dessus indique clairement que suffisamment d'octets pour accueillir 25
valeurs de type int devraient être disponibles.
Aussi, comme je l'ai mentionné, remarquez le casting. Cela convertit l'adresse, retournée
par la fonction en pointeur de type en pointeur int et int.
Malloc renvoie un pointeur de type void, c'est ce qu'il rend si utile.
Il renvoie cette adresse mais c'est un pointeur vide.
Rappelez-vous, nous avons parlé des pointeurs void et pourquoi les pointeurs void sont
utiles, car vous pouvez maintenant avoir une fonction qui peut prendre différents paramètres et qui peut
renvoyer un pointeur void. Maintenant, vous voyez le pouvoir
de renvoyer un pointeur vide, encore une
fois, mais pour l'utiliser, pour lui attribuer réellement des données, pour le dé-référencer, vous devez
le lancer.
Si nous ne voulions pas lancer cela, nous pourrions dire le numéro p étoile vide.
Nous ne le jetons pas, mais nous devrons éventuellement le lancer une
fois que nous aurions voulu le dé-référencer.
Mais c'est pourquoi nous devons lancer de malloc. Il renvoie une adresse et vous voulez qu'il pointe vers
un type int, et il renvoie un type void.
Vous pouvez demander n'importe quel nombre d'octets.
Cependant, cela dépend de votre système et de la quantité de mémoire dont vous disposez sur votre système.
Donc, si vous essayez de créer ou de demander une tonne d'octets ou s'il
y a une limitation, cela peut échouer.
Et quand il échoue, il retourne une valeur de null, malloc retournera null.
Et c'est bien parce que les pointeurs lorsque les pointeurs pointent sur null, cela signifie qu'ils
ne pointent vers aucune mémoire, vous ne
pouvez donc pas corrompre ou modifier une mémoire par inadvertance.
C'est donc toujours une bonne idée après avoir fait un
malloc de vérifier immédiatement la valeur null avant de l'utiliser,
comme c'est le cas lorsque vous
affectez des données ou comme c'est le cas lorsque vous dé-référencez un pointeur.
Vous voulez vous assurer que vous pointez réellement vers quelque chose.
Donc ici, nous allouons de la mémoire pour p numéro notre pointeur et
ensuite nous disons sinon p nombre.
N'oubliez pas que le nul est un zéro.
Vous pouvez donc simplement utiliser un pas dessus. Vous n'êtes pas obligé de le comparer à null.
Vous pouvez, vous pouvez le comparer à null, cela le rend plus lisible, peut-être le rend plus explicite.
Mais vous pouvez simplement dire non p nombre, et cela signifie en fait pas
un, et null est zéro. Donc, cela vérifiera null.
Et s'il est nul, cela signifie essentiellement que la mémoire
n'a pas pu être allouée. Si vous ne parvenez pas à allouer de la mémoire dans votre programme, vous avez des problèmes.
Et donc ce que vous devez généralement faire dans ce code pour gérer un
échec d'allocation de mémoire est que vous devez abandonner, vous devez
appeler exit, vous devez sortir de votre programme. Parce que si votre programme ne peut pas allouer de mémoire, votre programme est arrosé.
Donc, généralement, vous appelez un abandon accidentel si vous ne pouvez pas allouer de mémoire.
Au moins lorsque vous faites cela, vous pouvez afficher un message pour
terminer le programme. C'est bien mieux que de
permettre au programme de continuer alors que nous ne sommes pas en mesure d'allouer de la mémoire.
Parce qu'alors vous pouvez continuer, vous pouvez essayer d'utiliser cette mémoire,
elle sera nulle, donc le programme va se planter de toute façon
mais vous ne saurez pas nécessairement pourquoi.
Vous devez regarder la pile d'appels. Le débogage de cette erreur prendra beaucoup plus de temps.
Libérer la mémoire. Maintenant, j'ai mentionné que puisque
vous êtes le programmeur est responsable de l'allocation de la mémoire, vous devez vous
débarrasser de la mémoire lorsque vous ne l'utilisez plus car la mémoire est une ressource
vitale, en particulier sur les systèmes embarqués.
Vous ne devriez pas utiliser de mémoire si vous n'en avez pas besoin. Il faut donc libérer la mémoire.
Vous souhaitez toujours libérer la mémoire lorsqu'elle n'est plus nécessaire.
La mémoire que vous allouez sur le tas sera automatiquement libérée lorsque votre programme existe, ce qui
est bien car il effectue un nettoyage.
Mais il est préférable de libérer explicitement la mémoire lorsque vous en avez
terminé, même si c'est juste avant de quitter.
C'est plus
propre, faites-le toujours gratuitement ou libérez toujours la mémoire après l'avoir
allouée, vous voulez suivre cette règle. C'est une règle stricte et stricte.
Libérez toujours de la mémoire si vous l'allouez.
Une fuite de mémoire se produit lorsque vous allouez de la mémoire de manière dynamique et
que vous ne conservez pas la référence afin que vous ne puissiez pas la libérer.
Peut-être que vous êtes hors de portée pour cette référence que vous ne pouvez pas. Vous ne le relâchez pas.
C'est ce qu'on appelle une fuite de mémoire. Il y a de la mémoire allouée mais elle n'est plus utilisée.
Cela se produit souvent lorsque vous êtes dans une boucle et que vous allouez de la mémoire dans une boucle.
Vous êtes au courant. Vous allouez cette mémoire. Vous l'utilisez à l'intérieur de la
boucle, puis vous sautez hors de la boucle et vous ne la relâchez plus.
Chaque itération de boucle l'alloue.
Donc, pour libérer de la mémoire que vous avez allouée dynamiquement,
vous devez toujours avoir accès à cette adresse.
Donc, généralement, vous voulez une mémoire libre lorsque vous avez fini de l'utiliser et
généralement dans la méthode que vous l'avez allouée.
Cependant, si vous passez cette mémoire à une autre fonction et que c'est
la responsabilité de cette fonction, mais que vous souhaitez généralement libérer la mémoire et
la fonction que vous allouez.
Pour libérer la mémoire d'un bloc de mémoire
allouée dynamiquement dont vous avez stocké l'adresse dans un pointeur, il
suffit d'appeler la fonction libre.
C'est également dans la bibliothèque standard. Vous passez simplement le pointeur vers la fonction libre.
Et généralement, après avoir passé ce pointeur, vous voulez le définir sur null, car
cela signifie que vous n'avez pas de mémoire allouée pour lui.
La fonction libre a un paramètre formel de type void star,
vous pouvez donc passer n'importe quel type de
pointeur et cela fonctionnera.
Vous n'êtes pas obligé de le lancer ou quelque chose comme ça.
Tant que le nombre p contient l'adresse qui a été renvoyée lorsque la mémoire est allouée, le bloc
entier de mémoire sera libre pour une utilisation ultérieure. D'autres programmes peuvent utiliser cette mémoire, votre programme
peut désormais utiliser cette mémoire, ce qui
est important lorsque vous êtes sur un système embarqué.
Et puis, comme je l'ai mentionné, vous voulez toujours définir le
pointeur sur null pour signifier que ce pointeur ne pointe plus vers rien.
Cette mémoire a été libérée.
Il y a une autre fonction intéressante appelée calloc.
Cela présente quelques avantages par rapport à malloc. C'est très similaire à malloc,
mais ce qu'il fait, c'est qu'il alloue la mémoire en tant que nombre d'éléments dans une
taille donnée et qu'il initialise également la mémoire.
C'est la différence, l'initialisation. J'ai parlé de l'importance de l'initialisation.
C'est pourquoi vous
voulez toujours initialiser les données afin que vous compreniez qu'il y a
des données là-dedans que vous attendez.
Lorsque vous désinitialisez des données, il se peut qu'il y ait des données dont vous n'avez aucune idée.
Alors initialisez toujours vos données avec quelque chose.
Et le calloc est un peu meilleur car il le fait pour vous.
Il prend deux arguments, a deux paramètres.
Le nombre d'éléments de données pour lesquels de l'espace est requis, la taille de chaque élément de données.
Il est déclaré dans l'en-tête standard de la bibliothèque.
Vous l'appelez comme vous appelez malloc. Ressemble à peu près la même manière.
Dans cet exemple, nous créons un
pointeur entier et nous allouons 75 entiers de
sizeof int. Il faut donc deux paramètres.
C'est un peu plus propre car vous n'avez pas à faire une multiplication.
Dans malloc, vous deviez faire 75 événements de taille temporelle.
Ici, vous dites simplement 75, vous
créez 75 pouces et la taille de chacun est de 4 octets ou
tout ce que le système renvoie de la taille de l'opérateur.
Mais encore une fois, la signification de ceci est qu'elle va également initialiser toutes ces données, la valeur
de retour pourrait être nulle s'il n'est pas capable d'allouer cette mémoire, ce que vous ne devriez pas abandonner.
Mais c'est très similaire à l'utilisation de malloc, sauf que le gros avantage est cette initialisation.
Tous les entiers sont initialisés à zéro, et c'est un très gros avantage,
un gros avantage.
C'est aussi un peu plus facile à utiliser avec les deux paramètres.
Parlons d'une autre fonction appelée realloc.
La fonction realloc vous permet de réutiliser ou d'étendre la mémoire que vous avez
précédemment allouée à l'aide de malloc ou calloc.
C'est donc un moyen de modifier dynamiquement la quantité de mémoire allouée
pendant que vous exécutez.
Donc, vous pourriez initialement créer autant de mémoire et ensuite vous vous rendrez compte
que ce n'est pas assez de mémoire. Eh bien, ce que vous pouvez faire là-bas, c'est que vous
pouvez simplement appeler realloc, et cela réallouera la mémoire.
Il attend deux valeurs d'argument, un pointeur contenant une adresse qui a
été précédemment tournée par un appel à malloc.
Vous avez donc un pointeur qui a de la mémoire allouée ou calloc, puis la taille
et les octets de la nouvelle mémoire que vous souhaitez allouer.
Donc, cela allouera la quantité de mémoire que vous spécifiez par le
deuxième argument, c'est le deuxième argument. Il transfère le contenu de la mémoire précédemment
allouée, référencée par le pointeur, afin de savoir automatiquement la quantité de mémoire que vous
aviez précédemment allouée parce que vous passez le pointeur.
Et puis il ajoutera en fait à cela, la mémoire nouvellement allouée.
Et il renvoie un pointeur vide vers la nouvelle mémoire.
Vous devez donc le refondre lorsque vous appelez realloc comme les autres.
Il renvoie null si vous n'avez pas pu réallouer, ce
qui est probablement une erreur fatale et vous devez abandonner.
La caractéristique la plus importante de cette opération est que
la réallocation préserve le contenu de la zone de mémoire d'origine, vous étendez simplement cette mémoire.
Il peut être fragmenté parce que vous n'avez peut-être pas été en mesure de le faire dans
l'ordre, mais vous avez toujours toute cette mémoire que vous pouvez attribuer aux données actuelles.
Regardons un exemple.
Dans cet exemple, nous allons utiliser malloc, puis nous allons réallouer la mémoire et ensuite
nous allons la libérer.
Nous créons donc ici un pointeur de personnage.
Nous ne l'initialisons pas à null, nous devrions probablement l'être.
Mais il n'a pas de mémoire allouée à ce qu'il pointe.
Il a de la mémoire allouée pour
stocker le pointeur, qui n'est qu'une adresse. Mais si vous essayez d'aller à cette adresse, votre programme sera l'hôte.
Donc ce que vous faites, c'est que vous appelez malloc.
Ici, nous disons malloc 15.
Cela créera 15 octets pour un pointeur de caractère.
Nous le castons, car il renvoie un pointeur vide. Donc, str est maintenant un pointeur
qui a de la mémoire allouée pour ce sur quoi il pointe.
Nous pouvons ensuite utiliser str copy pour transmettre certaines données à ce vers quoi
elles pointent, une chaîne et
nous imprimons la chaîne dans l'adresse.
Eh bien, alors nous nous rendons compte que ce que nous avons alloué 15 octets
n'est pas assez grand.
Donc, nous appelons realloc, et nous passons dans
le pointeur qui est str et nous voulons réallouer 25 octets supplémentaires, puis cela renvoie un
point vide car nous devons le lancer à nouveau.
Maintenant, nous allons toujours avoir en mémoire Jason.
Alors maintenant, nous pouvons appeler string concatenated, et nous concaténons à la chaîne com,
donc maintenant ce sera Jason. com et ensuite nous l'imprimons.
Et puis le dernier, mais non le moindre, probablement la chose
la plus importante de cette diapositive, donc nous n'avons pas de fuite de mémoire, c'est que nous appelons free.
Appelez toujours gratuitement lorsque vous appelez malloc.
Jetons donc un œil à cet exemple à l'intérieur des blocs de code.
Voici notre exemple de code dans notre
éditeur de blocs de code.
Et la première chose que vous devriez remarquer est que
nous incluons le fichier d'en-tête lib standard parce que c'est là que se trouvent
nos fonctions malloc et nos fonctions realloc ainsi que notre fonction gratuite.
Aussi parce que nous utilisons certaines fonctions str, la concaténation et
la copie, nous incluons string. h.
Maintenant à la ligne huit, nous allons créer notre pointeur de caractère.
Il n'a pas de mémoire allouée pour cela.
Nous devrions initialiser ceci à null car
il ne pointe encore vers rien. Sur la ligne 11, nous appelons malloc.
Et nous devrions probablement dire 15 fois la
taille de char au cas où la taille
des octets de char serait différente, puis nous la convertirons
en pointeur de
caractère car malloc renvoie un vide. Nous le copions ensuite.
Si nous essayions de copier ceci et que nous ne faisions rien sur la ligne
11, le programme planterait parce que nous n'avons pas de mémoire allouée pour ce que la chaîne pointe.
Nous imprimons ensuite simplement la chaîne et l'adresse.
Maintenant, nous appelons realloc.
Nous devons stocker plus de données que 15
octets, donc nous allons réallouer avec
25 octets et nous voulons probablement refaire sizeof char pour
être cohérent.
Nous lançons ceci, puis nous concaténons sur notre emplacement
mémoire existant qui contient jason. com, et nous allons imprimer jason. com.
Et puis last but not least, à la ligne 20, nous libérons cette mémoire qui
est vraiment importante.
Alors allons-y et exécutons ceci. Et ce que vous verrez, c'est
que lorsque nous l'imprimons pour la deuxième fois, nous avons la concaténation.
Donc, notre première
chaîne, nous avons de la mémoire allouée et c'est une adresse 000C2918.
Ensuite, nous avons créé une nouvelle adresse en réallouant et en prenant la taille existante du pointeur que
nous lui avons passé en argument.
Ainsi, 000C0CD8 a en fait plus de données qui
lui sont allouées, les données
existantes en jason et 25 octets de plus.
Et donc ce que vous remarquez, c'est que la concaténation fonctionne.
Parce que lorsque nous avons
réaffecté, nous avons mis le contenu jason dans cette mémoire et nous l'avons simplement étendu.
Donc, beaucoup d'avantages à utiliser ces malloc, ces calloc et les appels de réallocation gratuits.
Vous voulez les utiliser, vous voulez les comprendre. Fonctions assez simples à utiliser.
Alors dans notre dernière diapositive, passons en revue quelques lignes directrices.
Vous voulez éviter d'allouer beaucoup de petites quantités de mémoire, ce n'est pas efficace.
Lorsque vous allouez de la mémoire
sur le tas, il y a une surcharge.
Et si vous allouez de petits blocs de mémoire, il y aura beaucoup plus de surcharge que
d'allouer moins de blocs plus gros.
Allouez donc la mémoire dont vous avez besoin,
mais ne le faites pas toutes les deux instructions avec réallocation, faites de
gros morceaux.
Ne vous accrochez à la mémoire que tant que vous en avez besoin. La mémoire est une ressource rare.
Quand c'est sur le tas, c'est encore plus rare.
Ainsi, lorsque vous avez terminé avec un bloc de mémoire, appelez gratuitement.
Assurez-vous toujours de prévoir la libération de la mémoire que vous avez allouée, décidez où dans
votre code vous libérerez la mémoire lorsque vous écrivez le code qui l'alloue.
Assurez-vous que vous avez accès à ce pointeur.
Si vous devez rendre le pointeur global, vous devez le faire.
Mais généralement, où que vous allouiez dans cette fonction, vous devez la libérer dans cette fonction.
Si vous avez d'autres fonctions libérant de la
mémoire pour vous, vous supposez alors que ce n'est pas bon. Il y a trop de couplage et de dépendances là-bas.
La personne qui le crée doit être la personne qui le supprime.
Ne mettez pas la responsabilité sur d'autres fonctions et autres.
Assurez-vous de ne pas écraser par inadvertance l'adresse de mémoire que vous avez allouée sur
le tas avant de l'avoir libérée, cela entraînerait une fuite de mémoire.
Donc, si vous essayez d'écrire trop d'octets ou si vous avez des débordements, vous pouvez
avoir des problèmes.
Soyez donc particulièrement prudent lorsque vous allouez de la mémoire dans une boucle
car cela peut entraîner des problèmes de dépassement de mémoire tampon.
J'espère que cela a été utile. L'allocation de mémoire dynamique est un gros problème.
Je vais vous donner un défi qui vous aidera à vous familiariser avec
cela, mais assurez-vous de bien comprendre ce concept. Merci.