Java a un GC automatique qui stoppe parfois le monde de temps en temps, mais prend en charge les déchets sur un tas. Maintenant, les applications C / C ++ ne gèlent pas STW, leur utilisation de la mémoire ne se développe pas non plus à l'infini. Comment ce comportement est-il atteint? Comment sont pris en charge les objets morts?
c++
c
garbage-collection
Ju Shua
la source
la source
new
.What happens to garbage in C++?
N'est-ce pas habituellement compilé dans un exécutable?Réponses:
Le programmeur est responsable de la
new
suppression des objets par lesquels il a créédelete
. Si un objet est créé, mais qu'il n'est pas détruit avant que le dernier pointeur ou la référence ne soit périmé, il tombe dans les fissures et devient une fuite de mémoire .Malheureusement pour C, C ++ et d'autres langages qui n'incluent pas de GC, cela ne fait que s'accumuler avec le temps. Une application ou le système risque de manquer de mémoire et d’allouer de nouveaux blocs de mémoire. À ce stade, l'utilisateur doit recourir à la fin de l'application pour que le système d'exploitation puisse récupérer cette mémoire utilisée.
En ce qui concerne la résolution de ce problème, plusieurs choses rendent la vie du programmeur beaucoup plus facile. Ceux-ci sont principalement pris en charge par la nature de la portée .
Ici, nous avons créé deux variables. Ils existent dans Block Scope , comme défini par les
{}
accolades. Lorsque l'exécution sort de cette portée, ces objets sont automatiquement supprimés. Dans ce cas,variableThatIsAPointer
comme son nom l'indique, est un pointeur sur un objet en mémoire. Lorsqu'il est hors de portée, le pointeur est supprimé, mais l'objet vers lequel il pointe reste. Ici, nous,delete
cet objet avant qu'il ne sorte du champ d'application, afin de nous assurer qu'il n'y a pas de fuite de mémoire. Cependant, nous aurions pu également passer ce pointeur ailleurs et nous espérer qu'il sera supprimé ultérieurement.Cette nature de la portée s'étend aux classes:
Ici, le même principe s'applique. Nous n'avons pas à nous soucier de
bar
quandFoo
est supprimé. CependantotherBar
, seul le pointeur est supprimé. SiotherBar
est le seul pointeur valide sur l’objet qu’il pointe, nous devrions probablement ledelete
placer dansFoo
le destructeur de. C'est le concept moteur de la RAIIRAII est également le moteur typique des pointeurs intelligents . Dans la bibliothèque standard sont ces C ++,
std::shared_ptr
,std::unique_ptr
etstd::weak_ptr
; Bien que j'ai vu et utilisé d'autresshared_ptr
/weak_ptr
mises en œuvre qui suivent les mêmes concepts. Pour ceux-ci, un compteur de références suit le nombre de pointeurs vers un objet donné, et est automatiquementdelete
affiché dès qu’il n’y a plus de référence.Au-delà de cela, tout se résume à des pratiques et à une discipline appropriées permettant au programmeur de s’assurer que son code traite correctement les objets.
la source
delete
- c'est ce que je cherchais. Impressionnant.delete
est automatiquement appelé pour vous par les pointeurs intelligents si vous les utilisez. Vous devez donc envisager de les utiliser chaque fois qu’un stockage automatique ne peut pas être utilisé.delete
dans votre code d'application (et à partir de C ++ 14, la même chose avecnew
), mais utilisez plutôt des pointeurs intelligents et RAII pour supprimer des objets de tas.std::unique_ptr
Le type et lastd::make_unique
fonction constituent le remplacement direct et le plus simple du code de l'applicationnew
etdelete
au niveau de celui-ci.C ++ n'a pas de récupération de place.
Les applications C ++ doivent se débarrasser de leurs propres déchets.
Les programmeurs d'applications C ++ doivent comprendre cela.
Quand ils oublient, le résultat s'appelle une "fuite de mémoire".
la source
new
etdelete
.malloc
etfree
, ounew[]
etdelete[]
ou tout autre allocateurs (comme Windows deGlobalAlloc
,LocalAlloc
,SHAlloc
,CoTaskMemAlloc
,VirtualAlloc
,HeapAlloc
, ...), et la mémoire allouée pour vous (par exemple viafopen
).En C, C ++ et autres systèmes sans Garbage Collector, le développeur et ses bibliothèques offrent des fonctionnalités pour indiquer quand la mémoire peut être récupérée.
L'installation la plus élémentaire est le stockage automatique . Plusieurs fois, le langage lui-même garantit que les articles sont éliminés:
Dans ce cas, le compilateur est chargé de savoir quand ces valeurs sont inutilisées et de récupérer le stockage qui leur est associé.
Lorsque vous utilisez le stockage dynamique , en C, la mémoire est traditionnellement allouée avec
malloc
et récupéréefree
. En C ++, la mémoire est traditionnellement allouée avecnew
et récupéréedelete
.C n'a pas beaucoup changé au fil des ans, mais C ++ moderne évite
new
etdelete
complètement et repose plutôt sur des installations bibliothèque (qui utilisent eux - mêmesnew
et de façondelete
appropriée):std::unique_ptr
etstd::shared_ptr
std::string
,std::vector
,std::map
, ... tout gérer en interne de manière transparente la mémoire allouée dynamiquementEn parlant de
shared_ptr
, il y a un risque: si un cycle de références est formé, et non rompu, il peut y avoir une fuite de mémoire. Il appartient au développeur d'éviter cette situation, le plus simple étant d'évitershared_ptr
complètement et le second, d'éviter les cycles au niveau du type.En conséquence, les fuites de mémoire ne sont pas un problème en C ++ , même pour les nouveaux utilisateurs, à condition qu'ils s'abstiennent d'utiliser
new
,delete
oustd::shared_ptr
. Ceci est différent de C où une discipline ferme est nécessaire et généralement insuffisante.Cependant, cette réponse ne serait pas complète sans mentionner la jumelle des fuites de mémoire: les pointeurs en suspens .
Un pointeur en suspens (ou une référence en suspens) est un danger créé en gardant un pointeur ou une référence à un objet mort. Par exemple:
L'utilisation d'un pointeur suspendu, ou référence, est un comportement indéfini . En général, heureusement, il s’agit d’un crash immédiat; malheureusement, cela provoque souvent une corruption de la mémoire en premier lieu ... et de temps en temps, un comportement étrange se présente parce que le compilateur émet un code vraiment étrange.
Le comportement non défini est le problème le plus important avec C et C ++ à ce jour, en termes de sécurité / exactitude des programmes. Vous voudrez peut-être consulter Rust pour une langue dépourvue de collecteur de place et de comportement non défini.
la source
new
,delete
etshared_ptr
"; sansnew
etshared_ptr
vous avez la propriété directe, donc pas de fuites. Bien sûr, vous risquez d'avoir des pointeurs en suspens, etc., mais j'ai bien peur que vous deviez quitter le C ++ pour vous en débarrasser.C ++ a cette chose appelée RAII . En gros, cela signifie que les ordures sont nettoyées au fur et à mesure plutôt que de les laisser en tas et de laisser le nettoyeur ranger après vous. (Imaginez-moi dans ma chambre en train de regarder le ballon de foot. Alors que je bois des canettes de bière et que j'ai besoin de nouvelles canettes, la méthode C ++ consiste à amener la canette vide à la corbeille en direction du réfrigérateur, la méthode C # consiste à la jeter par terre. et attendez que la femme de ménage vienne les chercher quand elle vient faire le ménage).
Il est maintenant possible de perdre de la mémoire en C ++, mais pour ce faire, vous devez quitter les constructions habituelles et revenir à la méthode C: allouer un bloc de mémoire et garder la trace de ce bloc sans assistance linguistique. Certaines personnes oublient ce pointeur et ne peuvent donc pas supprimer le bloc.
la source
Il convient de noter que, dans le cas de C ++, il est souvent mal compris que "vous devez gérer manuellement la mémoire". En fait, votre code ne gère généralement pas la mémoire.
Objets de taille fixe (avec durée de vie)
Dans la grande majorité des cas, lorsque vous avez besoin d'un objet, celui-ci aura une durée de vie définie dans votre programme et sera créé sur la pile. Cela fonctionne pour tous les types de données primitifs intégrés, mais aussi pour les instances de classes et de structures:
Les objets empilés sont automatiquement supprimés à la fin de la fonction. En Java, les objets sont toujours créés sur le tas et doivent donc être supprimés par un mécanisme tel que le garbage collection. Ceci n'est pas un problème pour les objets de pile.
Objets gérant des données dynamiques (avec durée de vie)
L'utilisation de l'espace sur la pile fonctionne pour les objets de taille fixe. Lorsque vous avez besoin d'une quantité variable d'espace, telle qu'un tableau, une autre approche est utilisée: la liste est encapsulée dans un objet de taille fixe qui gère la mémoire dynamique à votre place. Cela fonctionne car les objets peuvent avoir une fonction de nettoyage spéciale, le destructeur. Il est garanti d'être appelé lorsque l'objet sort du domaine et fait l'inverse du constructeur:
Il n'y a pas du tout de gestion de mémoire dans le code où la mémoire est utilisée. La seule chose dont nous devons nous assurer est que l'objet que nous avons écrit possède un destructeur approprié. Peu importe la façon dont nous laissons les choses en place
listTest
, que ce soit via une exception ou simplement en y retournant, le destructeur~MyList()
sera appelé et nous n’aurons pas besoin de gérer de mémoire.(Je pense que c'est une décision de conception amusante d'utiliser l' opérateur binaire NOT
~
, pour indiquer le destructeur. Lorsqu'il est utilisé sur des nombres, il inverse les bits; par analogie, cela indique que ce que le constructeur a fait est inversé.)Fondamentalement, tous les objets C ++ qui ont besoin de mémoire dynamique utilisent cette encapsulation. Cela a été appelé RAII ("l’acquisition des ressources est une initialisation"), ce qui est assez étrange pour exprimer la simple idée que les objets se soucient de leurs propres contenus; ce qu'ils acquièrent, c'est à eux de les nettoyer.
Objets polymorphes et durée de vie au-delà de la portée
Ces deux cas concernaient une mémoire dont la durée de vie est clairement définie: la durée de vie est identique à la portée. Si nous ne voulons pas qu'un objet expire lorsque nous quittons la portée, il existe un troisième mécanisme qui peut gérer la mémoire pour nous: un pointeur intelligent. Les pointeurs intelligents sont également utilisés lorsque vous avez des instances d'objets dont le type varie au moment de l'exécution, mais qui ont une interface ou une classe de base commune:
Il existe un autre type de pointeur intelligent
std::shared_ptr
permettant de partager des objets entre plusieurs clients. Ils ne suppriment leur objet contenu que lorsque le dernier client est hors de portée. Ils peuvent donc être utilisés dans des situations dans lesquelles on ignore totalement le nombre de clients et la durée d'utilisation de l'objet.En résumé, nous voyons que vous ne faites pas vraiment de gestion de mémoire manuelle. Tout est encapsulé et est ensuite pris en charge au moyen d'une gestion de la mémoire entièrement automatique et basée sur la portée. Dans les cas où cela ne suffit pas, des pointeurs intelligents sont utilisés pour encapsuler la mémoire brute.
Il est considéré comme une très mauvaise pratique d’utiliser des pointeurs bruts en tant que propriétaires de ressources n’importe où dans le code C ++, des allocations brutes en dehors des constructeurs et des
delete
appels bruts en dehors des destructeurs, car ils sont presque impossibles à gérer en cas d’exception et généralement difficiles à utiliser en toute sécurité.Le meilleur: cela fonctionne pour tous les types de ressources
L’un des principaux avantages de RAII est qu’il ne se limite pas à la mémoire. Il fournit en fait un moyen très naturel de gérer des ressources telles que des fichiers et des sockets (ouverture / fermeture) et des mécanismes de synchronisation tels que des mutex (verrouillage / déverrouillage). Fondamentalement, toutes les ressources pouvant être acquises et devant être libérées sont gérées exactement de la même manière en C ++, et aucune partie de cette gestion n'est laissée à l'utilisateur. Tout cela est encapsulé dans des classes qui acquièrent dans le constructeur et libèrent dans le destructeur.
Par exemple, une fonction verrouillant un mutex est généralement écrite comme ceci en C ++:
D'autres langues compliquent beaucoup les choses, en vous demandant de le faire manuellement (dans une
finally
clause, par exemple ) ou en générant des mécanismes spécialisés qui résolvent ce problème, mais pas de manière particulièrement élégante (généralement plus tard dans la vie, lorsque suffisamment de souffert de la lacune). Ces mécanismes sont try-with-resources en Java et l' instruction using en C #, qui sont tous deux des approximations de la norme RAII de C ++.Donc, pour résumer, tout ceci était un compte-rendu très superficiel de RAII en C ++, mais j'espère que cela aidera les lecteurs à comprendre que la gestion de la mémoire et même des ressources en C ++ n'est généralement pas "manuelle", mais en réalité essentiellement automatique.
la source
delete
ça et vous êtes mort", les réponses dépassent les 30 points et sont acceptées, alors que celui-ci en compte cinq. Est-ce que quelqu'un utilise réellement le C ++ ici?En ce qui concerne plus particulièrement le langage C, le langage ne vous fournit aucun outil pour gérer la mémoire allouée de manière dynamique. Vous êtes absolument responsable de vous assurer que chaque personne
*alloc
a un correspondantfree
quelque part.Là où les choses se compliquent vraiment, c’est quand une allocation de ressources échoue à mi-parcours; essayez-vous à nouveau, annulez-vous et recommencez-vous depuis le début, annulez-vous et sortez-vous avec une erreur, laissez-vous vous échapper et laissez-vous le système d'exploitation s'en charger?
Par exemple, voici une fonction pour allouer un tableau 2D non contigu. Le comportement ici est que si une défaillance d'allocation se produit au milieu du processus, nous annulons tout et renvoyons une indication d'erreur à l'aide d'un pointeur NULL:
Ce code est assez moche avec ceux-là
goto
, mais, en l'absence de tout mécanisme structuré de gestion des exceptions, c'est quasiment le seul moyen de traiter le problème sans tout renverser, en particulier si votre code d'allocation de ressources est imbriqué. d'une boucle de profondeur. C’est l’une des rares fois oùgoto
c’est une option attrayante; sinon, vous utilisez un groupe de drapeaux et d’if
énoncés supplémentaires .Vous pouvez vous simplifier la vie en écrivant des fonctions d'allocateur / de désallocateur dédiées pour chaque ressource, par exemple:
la source
goto
déclarations. Ceci est une pratique recommandée dans certaines régions. C'est un schéma couramment utilisé pour se protéger contre l'équivalent d'exceptions de C. Observez le code du noyau Linux, qui regorge d'goto
instructions - et qui ne fuit pas.goto
est étrangère. Ce serait plus lisible si vous changiezgoto done;
enreturn arr;
etarr=NULL;done:return arr;
enreturn NULL;
. Bien que dans des cas plus complexes, il se peut qu’il y ait plusieursgoto
s commençant à se dérouler à des niveaux de disponibilité différents (ce qui serait fait par un déroulement de pile d’exceptions en C ++).J'ai appris à classer les problèmes de mémoire dans un certain nombre de catégories différentes.
Une fois gouttes. Supposons qu'un programme perd 100 octets au démarrage, mais ne fuira plus jamais. Poursuivre et éliminer ces fuites non récurrentes est bien (j'aime bien avoir un rapport vierge par une fonction de détection de fuites) mais n'est pas essentiel. Parfois, il faut s'attaquer à des problèmes plus importants.
Fuites répétées. Une fonction appelée de manière répétitive au cours de la durée de vie d’un programme qui perd régulièrement de la mémoire est un gros problème. Ces gouttes vont torturer à mort le programme, et éventuellement le système d'exploitation.
Références mutuelles. Si les objets A et B se référencent via des pointeurs partagés, vous devez faire quelque chose de spécial, que ce soit dans la conception de ces classes ou dans le code qui implémente / utilise ces classes pour rompre la circularité. (Ce n'est pas un problème pour les langages collectés.)
Se souvenir trop. C'est le cousin maléfique des fuites d'ordures / mémoire. RAII ne va pas aider ici, pas plus que la collecte des ordures. C'est un problème dans n'importe quelle langue. Si une variable active a un chemin qui la connecte à un bloc de mémoire aléatoire, ce bloc de mémoire aléatoire n'est pas gâché. Faire en sorte qu'un programme devienne oublieux et qu'il puisse durer plusieurs jours est délicat. Créer un programme pouvant durer plusieurs mois (par exemple, jusqu'à ce que le disque tombe en panne) est très, très délicat.
Je n'ai pas eu de problème sérieux avec les fuites pendant très longtemps. L'utilisation de RAII en C ++ aide beaucoup à remédier à ces écoulements et fuites. (Il faut toutefois être prudent avec les pointeurs partagés.) Plus important encore, j'ai eu des problèmes avec des applications dont l'utilisation de la mémoire ne cesse de croître, en raison de connexions non exploitées à la mémoire qui ne sont plus d'aucune utilité.
la source
Il appartient au programmeur C ++ d'implémenter sa propre forme de récupération de place si nécessaire. Sinon, vous obtiendrez ce que l'on appelle une «fuite de mémoire». Il est assez courant que les langages «de haut niveau» (tels que Java) aient un garbage collection intégré, mais pas les langages «de bas niveau» tels que C et C ++.
la source