J'ai toujours entendu dire qu'en C, il faut vraiment regarder comment on gère la mémoire. Et je commence encore à apprendre le C, mais jusqu'à présent, je n'ai pas eu à faire de mémoire pour gérer des activités connexes. J'ai toujours imaginé devoir libérer des variables et faire toutes sortes de choses horribles. Mais cela ne semble pas être le cas.
Quelqu'un peut-il me montrer (avec des exemples de code) un exemple de quand vous auriez à faire de la "gestion de la mémoire"?
Réponses:
Il y a deux endroits où les variables peuvent être mises en mémoire. Lorsque vous créez une variable comme celle-ci:
Les variables sont créées dans la " pile ". Les variables de pile sont automatiquement libérées lorsqu'elles sont hors de portée (c'est-à-dire lorsque le code ne peut plus les atteindre). Vous pourriez les entendre appelées variables "automatiques", mais cela n'est plus à la mode.
De nombreux exemples pour débutants n'utiliseront que des variables de pile.
La pile est agréable parce qu'elle est automatique, mais elle a aussi deux inconvénients: (1) Le compilateur doit savoir à l'avance la taille des variables, et (b) l'espace de pile est quelque peu limité. Par exemple: dans Windows, sous les paramètres par défaut de l'éditeur de liens Microsoft, la pile est définie sur 1 Mo et elle n'est pas entièrement disponible pour vos variables.
Si vous ne savez pas au moment de la compilation la taille de votre tableau, ou si vous avez besoin d'un grand tableau ou structure, vous avez besoin du "plan B".
Le plan B est appelé le « tas ». Vous pouvez généralement créer des variables aussi grandes que le système d'exploitation vous le permet, mais vous devez le faire vous-même. Les publications précédentes vous ont montré une façon de le faire, bien qu'il existe d'autres façons:
(Notez que les variables du tas ne sont pas manipulées directement, mais via des pointeurs)
Une fois que vous avez créé une variable de tas, le problème est que le compilateur ne peut pas dire quand vous en avez terminé, vous perdez donc la libération automatique. C'est là qu'intervient la "libération manuelle" dont vous parliez. Votre code est maintenant responsable de décider quand la variable n'est plus nécessaire, et de la libérer pour que la mémoire puisse être utilisée à d'autres fins. Pour le cas ci-dessus, avec:
Ce qui rend cette deuxième option "mauvaise affaire", c'est qu'il n'est pas toujours facile de savoir quand la variable n'est plus nécessaire. Si vous oubliez de publier une variable lorsque vous n'en avez pas besoin, votre programme consommera plus de mémoire dont il a besoin. Cette situation s'appelle une «fuite». La mémoire «perdue» ne peut pas être utilisée pour quoi que ce soit jusqu'à ce que votre programme se termine et que le système d'exploitation récupère toutes ses ressources. Des problèmes encore plus désagréables sont possibles si vous libérez une variable de tas par erreur avant d'en avoir fini avec elle.
En C et C ++, vous êtes responsable de nettoyer vos variables de tas comme indiqué ci-dessus. Cependant, il existe des langages et des environnements tels que Java et .NET comme C # qui utilisent une approche différente, où le tas est nettoyé tout seul. Cette deuxième méthode, appelée "garbage collection", est beaucoup plus facile pour le développeur mais vous payez une pénalité en frais généraux et en performances. C'est un équilibre.
(J'ai passé sous silence de nombreux détails pour donner une réponse plus simple, mais j'espère plus nivelée)
la source
malloc()
, sa cause UB,(char *)malloc(size);
voir stackoverflow.com/questions/605845/…Voici un exemple. Supposons que vous ayez une fonction strdup () qui duplique une chaîne:
Et vous l'appelez comme ceci:
Vous pouvez voir que le programme fonctionne, mais vous avez alloué de la mémoire (via malloc) sans la libérer. Vous avez perdu votre pointeur vers le premier bloc de mémoire lorsque vous avez appelé strdup la deuxième fois.
Ce n'est pas grave pour cette petite quantité de mémoire, mais considérez le cas:
Vous avez maintenant utilisé 11 Go de mémoire (peut-être plus, selon votre gestionnaire de mémoire) et si vous n'avez pas planté, votre processus s'exécute probablement assez lentement.
Pour résoudre le problème, vous devez appeler free () pour tout ce qui est obtenu avec malloc () après avoir fini de l'utiliser:
J'espère que cet exemple vous aidera!
la source
Vous devez effectuer une "gestion de la mémoire" lorsque vous souhaitez utiliser la mémoire sur le tas plutôt que sur la pile. Si vous ne savez pas quelle taille créer un tableau avant l'exécution, vous devez utiliser le tas. Par exemple, vous souhaiterez peut-être stocker quelque chose dans une chaîne, mais vous ne savez pas quelle sera la taille de son contenu tant que le programme ne sera pas exécuté. Dans ce cas, vous écririez quelque chose comme ceci:
la source
Je pense que la manière la plus concise de répondre à la question est de considérer le rôle du pointeur dans C. Le pointeur est un mécanisme léger mais puissant qui vous donne une immense liberté au prix d'une immense capacité à vous tirer une balle dans le pied.
En C, la responsabilité de s'assurer que vos pointeurs pointent vers la mémoire que vous possédez est à vous et à vous seul. Cela nécessite une approche organisée et disciplinée, à moins que vous n'abandonniez les pointeurs, ce qui rend difficile la rédaction de C.
Les réponses publiées à ce jour se concentrent sur les allocations de variables automatiques (pile) et de tas. L'utilisation de l'allocation de pile permet une mémoire gérée automatiquement et pratique, mais dans certaines circonstances (grands tampons, algorithmes récursifs), cela peut conduire au terrible problème de débordement de pile. Savoir exactement combien de mémoire vous pouvez allouer sur la pile dépend beaucoup du système. Dans certains scénarios intégrés, quelques dizaines d'octets peuvent être votre limite, dans certains scénarios de bureau, vous pouvez utiliser en toute sécurité des mégaoctets.
L'allocation de tas est moins inhérente au langage. Il s'agit essentiellement d'un ensemble d'appels de bibliothèque qui vous accorde la propriété d'un bloc de mémoire d'une taille donnée jusqu'à ce que vous soyez prêt à le renvoyer («libre»). Cela semble simple, mais est associé à un chagrin incalculable du programmeur. Les problèmes sont simples (libérer la même mémoire deux fois, ou pas du tout [fuites de mémoire], ne pas allouer suffisamment de mémoire [buffer overflow], etc.) mais difficiles à éviter et à déboguer. Une approche très disciplinée est absolument obligatoire dans la pratique, mais bien sûr, la langue ne l'exige pas.
Je voudrais mentionner un autre type d'allocation de mémoire qui a été ignoré par d'autres messages. Il est possible d'allouer statiquement des variables en les déclarant en dehors de toute fonction. Je pense qu'en général, ce type d'allocation a une mauvaise réputation car il est utilisé par des variables globales. Cependant, rien n'indique que la seule façon d'utiliser la mémoire allouée de cette manière est une variable globale indisciplinée dans un désordre de code spaghetti. La méthode d'allocation statique peut être utilisée simplement pour éviter certains des pièges du tas et des méthodes d'allocation automatique. Certains programmeurs C sont surpris d'apprendre que de grands programmes sophistiqués intégrés et de jeux C ont été construits sans aucune allocation de tas.
la source
Il y a quelques bonnes réponses ici sur la façon d'allouer et de libérer de la mémoire, et à mon avis, le côté le plus difficile de l'utilisation de C est de s'assurer que la seule mémoire que vous utilisez est la mémoire que vous avez allouée - si cela n'est pas fait correctement, vous terminez up with est le cousin de ce site - un débordement de tampon - et vous pouvez être en train d'écraser la mémoire utilisée par une autre application, avec des résultats très imprévisibles.
Un exemple:
À ce stade, vous avez alloué 5 octets pour myString et l'avez rempli avec "abcd \ 0" (les chaînes se terminent par un nul - \ 0). Si votre allocation de chaîne était
Vous attribueriez "abcde" dans les 5 octets que vous avez alloués à votre programme, et le caractère nul de fin serait placé à la fin de ceci - une partie de la mémoire qui n'a pas été allouée pour votre utilisation et pourrait être gratuit, mais pourrait également être utilisé par une autre application - C'est la partie critique de la gestion de la mémoire, où une erreur aura des conséquences imprévisibles (et parfois non répétables).
la source
strcpy()
place de=
; Je suppose que c'était l'intention de Chris BC.Une chose à retenir est de toujours initialiser vos pointeurs à NULL, car un pointeur non initialisé peut contenir une adresse mémoire valide pseudo-aléatoire qui peut provoquer des erreurs de pointeur en silence. En imposant un pointeur à initialiser avec NULL, vous pouvez toujours détecter si vous utilisez ce pointeur sans l'initialiser. La raison est que les systèmes d'exploitation "câblent" l'adresse virtuelle 0x00000000 à des exceptions de protection générales pour intercepter l'utilisation du pointeur nul.
la source
Vous pouvez également utiliser l'allocation de mémoire dynamique lorsque vous devez définir un énorme tableau, par exemple int [10000]. Vous ne pouvez pas simplement le mettre dans la pile parce qu'alors, hm ... vous aurez un débordement de pile.
Un autre bon exemple serait une implémentation d'une structure de données, disons une liste chaînée ou un arbre binaire. Je n'ai pas d'exemple de code à coller ici, mais vous pouvez le rechercher facilement sur Google.
la source
(J'écris parce que je pense que les réponses jusqu'à présent ne sont pas tout à fait exactes.)
La raison pour laquelle vous devez mentionner la gestion de la mémoire est lorsque vous avez un problème / une solution qui vous oblige à créer des structures complexes. (Si vos programmes plantent si vous allouez à la fois beaucoup d'espace sur la pile, c'est un bogue.) Typiquement, la première structure de données que vous aurez besoin d'apprendre est une sorte de liste . En voici un seul lié, du haut de ma tête:
Naturellement, vous aimeriez quelques autres fonctions, mais c'est pour cela que vous avez besoin de la gestion de la mémoire. Je dois souligner qu'il existe un certain nombre de trucs qui sont possibles avec la gestion de la mémoire "manuelle", par exemple,
Obtenez un bon débogueur ... Bonne chance!
la source
À Euro Micelli
Un point négatif à ajouter est que les pointeurs vers la pile ne sont plus valides lorsque la fonction est renvoyée, vous ne pouvez donc pas renvoyer un pointeur vers une variable de pile à partir d'une fonction. C'est une erreur courante et une des principales raisons pour lesquelles vous ne pouvez pas vous en tirer avec juste des variables de pile. Si votre fonction a besoin de renvoyer un pointeur, alors vous devez malloc et vous occuper de la gestion de la mémoire.
la source
Vous avez raison, bien sûr. Je pense que cela a toujours été vrai, même si je n'ai pas de copie de K&R à vérifier.
Je n'aime pas beaucoup les conversions implicites en C, donc j'ai tendance à utiliser des transtypages pour rendre la «magie» plus visible. Parfois, cela aide à la lisibilité, parfois non, et parfois cela provoque un bogue silencieux détecté par le compilateur. Pourtant, je n'ai pas d'opinion tranchée à ce sujet, d'une manière ou d'une autre.
Ouais ... tu m'as attrapé là-bas. Je passe beaucoup plus de temps en C ++ qu'en C. Merci de l'avoir remarqué.
la source
En C, vous avez en fait deux choix différents. Premièrement, vous pouvez laisser le système gérer la mémoire pour vous. Sinon, vous pouvez le faire vous-même. En règle générale, vous voudrez vous en tenir au premier aussi longtemps que possible. Cependant, la mémoire gérée automatiquement en C est extrêmement limitée et vous devrez gérer manuellement la mémoire dans de nombreux cas, tels que:
une. Vous voulez que la variable survive aux fonctions et vous ne voulez pas avoir de variable globale. ex:
b. vous voulez avoir de la mémoire allouée dynamiquement. L'exemple le plus courant est un tableau sans longueur fixe:
Vous voyez, une valeur longue est suffisante pour contenir TOUT. N'oubliez pas de le libérer, sinon vous le regretterez. C'est l'une de mes astuces préférées pour m'amuser en C: D.
Cependant, en général, vous voudrez rester à l'écart de vos astuces préférées (T___T). Vous briserez votre système d'exploitation, tôt ou tard, si vous les utilisez trop souvent. Tant que vous n'utilisez pas * alloc et free, il est prudent de dire que vous êtes encore vierge et que le code est toujours beau.
la source
Sûr. Si vous créez un objet qui existe en dehors de la portée dans laquelle vous l'utilisez. Voici un exemple artificiel (gardez à l'esprit que ma syntaxe sera désactivée; mon C est rouillé, mais cet exemple illustrera toujours le concept):
Dans cet exemple, j'utilise un objet de type SomeOtherClass pendant la durée de vie de MyClass. L'objet SomeOtherClass est utilisé dans plusieurs fonctions, j'ai donc alloué dynamiquement la mémoire: l'objet SomeOtherClass est créé lorsque MyClass est créé, utilisé plusieurs fois au cours de la vie de l'objet, puis libéré une fois MyClass libéré.
Évidemment, s'il s'agissait de code réel, il n'y aurait aucune raison (à part éventuellement de la consommation de mémoire de la pile) de créer myObject de cette manière, mais ce type de création / destruction d'objet devient utile lorsque vous avez beaucoup d'objets, et que vous voulez contrôler finement lorsqu'ils sont créés et détruits (pour que votre application n'aspire pas 1 Go de RAM pendant toute sa durée de vie, par exemple), et dans un environnement fenêtré, c'est à peu près obligatoire, en tant qu'objets que vous créez (boutons, par exemple) , doivent exister bien en dehors de la portée de toute fonction particulière (ou même de classe).
la source