web-dev-qa-db-fra.com

Pourquoi C ++ n’a-t-il pas de ramasse-miettes?

Je ne pose pas cette question à cause des mérites de la collecte des ordures tout d’abord. Ma principale raison de poser cette question est que je sais que Bjarne Stroustrup a déclaré que C++ aurait un ramasse-miettes à un moment donné.

Cela dit, pourquoi n'a-t-il pas été ajouté? Il existe déjà des éboueurs pour C++. Est-ce juste une de ces choses du type "plus facile à dire qu'à faire"? Ou y a-t-il d'autres raisons pour lesquelles il n'a pas été ajouté (et ne sera pas ajouté en C++ 11)?

Liens croisés:

Juste pour clarifier, je comprends les raisons pour lesquelles C++ n’avait pas de ramasse-miettes lorsqu’il a été créé. Je me demande pourquoi le collectionneur ne peut pas être ajouté.

256
Jason Baker

La collecte implicite des ordures aurait pu être ajoutée, mais elle n’a tout simplement pas abouti. Probablement en raison non seulement de complications liées à la mise en œuvre, mais également du fait que les personnes ne peuvent pas parvenir à un consensus général assez rapidement.

Une citation de Bjarne Stroustrup lui-même:

J'espérais qu'un récupérateur de mémoire pouvant éventuellement être activé ferait partie de C++ 0x, mais il y avait suffisamment de problèmes techniques pour que je puisse me contenter d'une spécification détaillée de la manière dont un tel collecteur s'intègre au reste du langage. , si fourni. Comme c'est le cas pour pratiquement toutes les fonctionnalités C++ 0x, une implémentation expérimentale existe.

Il y a une bonne discussion sur le sujet ici .

Aperçu général:

C++ est très puissant et vous permet de faire presque n'importe quoi. Pour cette raison, il ne génère pas automatiquement beaucoup d'éléments susceptibles d'avoir une incidence sur les performances. La récupération de place peut être facilement mise en œuvre avec des pointeurs intelligents (objets qui enveloppent les pointeurs avec un nombre de références, qui se suppriment automatiquement eux-mêmes lorsque le nombre de références atteint 0).

C++ a été conçu pour les concurrents qui n’ont pas de système de collecte des ordures. L’efficacité était la principale préoccupation à laquelle le C++ devait faire face, comparé à C et d’autres.

Il existe 2 types de poubelles ...

Collecte de place explicite:

C++ 0x aura un ramasse-miettes via des pointeurs créés avec shared_ptr

Si vous le voulez, vous pouvez l'utiliser, si vous ne le voulez pas, vous n'êtes pas obligé de l'utiliser.

Vous pouvez aussi utiliser boost: shared_ptr si vous ne voulez pas attendre C++ 0x.

Collecte de place implicite:

Il n'a pas de récupération de déchets transparente cependant. Ce sera toutefois un point central pour les futures spécifications C++.

Pourquoi Tr1 n'a pas de récupération de place implicite?

Il y a beaucoup de choses que tr1 de C++ 0x aurait dû avoir, Bjarne Stroustrup dans des entretiens précédents a déclaré que tr1 n'en avait pas autant qu'il l'aurait souhaité.

150
Brian R. Bondy

Pour ajouter au débat ici.

Il existe des problèmes connus avec la récupération de place, et leur compréhension permet de comprendre pourquoi il n'en existe pas en C++.

1. Performance?

La première plainte concerne souvent les performances, mais la plupart des gens ne réalisent pas vraiment de quoi ils parlent. Comme illustré par Martin Beckett, le problème n'est peut-être pas la performance en soi, mais la prévisibilité de la performance.

Il existe actuellement 2 familles de GC largement déployées:

  • Genre Mark-And-Sweep
  • Type de comptage de référence

Le Mark And Sweep est plus rapide (moins d’impact sur les performances globales), mais il souffre d’un syndrome du "gel du monde": c’est-à-dire que lorsque le GC démarre, tout le reste est arrêté jusqu’à ce que le GC ait terminé son nettoyage. Si vous souhaitez construire un serveur qui réponde en quelques millisecondes ... certaines transactions ne seront pas à la hauteur de vos attentes :)

Le problème de Reference Counting est différent: le comptage de références ajoute une surcharge, en particulier dans les environnements multi-threading, car vous devez avoir un décompte atomique. De plus, il y a le problème des cycles de référence, vous avez donc besoin d'un algorithme astucieux pour détecter ces cycles et les éliminer (généralement implémenté par un "gel du monde" aussi, bien que moins fréquent). En général, à partir d’aujourd’hui, ce type (même s’il est normalement plus réactif ou plutôt, se figer moins souvent) est plus lent que le Mark And Sweep.

J'ai vu un document de développeurs Eiffel qui essayaient d'implémenter un Reference Counting Garbage Collector qui aurait une performance globale similaire à Mark And Sweep sans l'aspect "Freeze The World". Il fallait un thread séparé pour le CPG (typique). L’algorithme était un peu effrayant (à la fin), mais le document a réussi à introduire les concepts un à un et à montrer l’évolution de l’algorithme, de la version "simple" à la version complète. Lecture recommandée si seulement je pouvais remettre mes mains sur le fichier PDF ...

2. L'acquisition des ressources est une initialisation

Il est courant dans C++ que vous encapsuliez la propriété des ressources dans un objet pour vous assurer qu'elles sont correctement libérées. Il est principalement utilisé pour la mémoire car nous n'avons pas de récupération de place, mais il est également utile dans de nombreuses autres situations:

  • verrous (multi-thread, descripteur de fichier, ...)
  • connexions (vers une base de données, un autre serveur, ...)

L'idée est de contrôler correctement la durée de vie de l'objet:

  • il devrait être en vie aussi longtemps que vous en avez besoin
  • il devrait être tué quand vous avez fini avec elle

Le problème de la GC est que, si cela aide le premier et garantit en fin de compte que plus tard ... cet "ultime" ne sera peut-être pas suffisant. Si vous libérez un verrou, vous aimeriez vraiment qu'il soit libéré maintenant, afin qu'il ne bloque plus d'appels!

Les langues avec GC ont deux solutions:

  • n'utilisez pas GC lorsque l'allocation de pile est suffisante: c'est normalement pour des problèmes de performances, mais dans notre cas cela aide vraiment puisque la portée définit la durée de vie
  • using construct ... mais c'est explicite (faible) RAII alors que dans C++, RAII est implicite, de sorte que l'utilisateur NE PEUT PAS commettre l'erreur par mégarde (en omettant le mot clé using)

. Smart Pointers

Les pointeurs intelligents apparaissent souvent comme une solution miracle pour gérer la mémoire dans C++. Souvent, j'ai entendu dire: nous n'avons pas besoin du GC après tout, car nous avons des indicateurs intelligents.

On ne peut pas avoir plus tort.

Les pointeurs intelligents aident: auto_ptr et unique_ptr utilisent les concepts RAII, extrêmement utiles. Ils sont si simples que vous pouvez les écrire vous-même assez facilement.

Lorsque vous devez partager la propriété, cela devient plus difficile: vous pouvez partager plusieurs threads et il existe quelques problèmes subtils avec le traitement du nombre. Par conséquent, on va naturellement vers shared_ptr.

C'est génial, c'est pour ça que Boost est au final, mais ce n'est pas une solution miracle. En fait, le problème principal avec shared_ptr est qu’il émule un GC implémenté par Reference Counting, mais vous devez implémenter la détection de cycle par vous-même ... Urg

Bien sûr, il y a ce weak_ptr, mais j'ai malheureusement déjà déjà vu des fuites de mémoire malgré l'utilisation de shared_ptr à cause de ces cycles ... et lorsque vous êtes dans un environnement multi-thread, il est extrêmement difficile de détecter!

4. Quelle est la solution?

Il n’ya pas de solution miracle, mais comme toujours, c’est faisable. En l'absence de GC, il faut définir clairement la propriété:

  • préférer avoir un seul propriétaire à un moment donné, si possible
  • sinon, assurez-vous que votre diagramme de classes n'a pas de cycle relatif à la propriété et cassez-le avec une application subtile de weak_ptr

Alors, en effet, ce serait bien d’avoir un GC ... mais ce n’est pas une question triviale. Et dans le même temps, nous avons juste besoin de nous retrousser les manches.

139
Matthieu M.

Quel genre? Devrait-il être optimisé pour les contrôleurs de machine à laver intégrés, les téléphones portables, les stations de travail ou les supercalculateurs?
Devrait-il accorder la priorité à la réactivité de l’interface graphique ou au chargement du serveur?
devrait-il utiliser beaucoup de mémoire ou beaucoup de processeur?

C/c ++ est utilisé dans trop de circonstances différentes. Je soupçonne que quelque chose comme des pointeurs intelligents boost sera suffisant pour la plupart des utilisateurs

Éditer - Les éboueurs automatiques ne sont pas vraiment un problème de performances (vous pouvez toujours acheter plus de serveur), c'est une question de performances prévisibles.
Ne pas savoir quand le GC entrera en fonction, c'est comme employer un pilote de ligne aérienne narcoleptique, la plupart du temps, ils sont géniaux - mais quand vous avez vraiment besoin de réactivité!

54
Martin Beckett

L'une des principales raisons pour lesquelles C++ ne possède pas de système de récupération de place intégré est qu'il est très difficile de le faire jouer à Nice avec des destructeurs. Pour autant que je sache, personne ne sait vraiment comment le résoudre complètement. Il y a beaucoup de problèmes à traiter:

  • durée de vie déterministe des objets (le comptage de références vous donne ceci, mais pas le GC. Bien que ce ne soit peut-être pas si grave).
  • qu'advient-il si un destructeur jette quand l'objet est en train d'être ramassé? La plupart des langages ignorent cette exception, car il n'y a vraiment pas de bloc catch pour pouvoir le transporter, mais ce n'est probablement pas une solution acceptable pour C++.
  • Comment l'activer/le désactiver? Naturellement, ce serait probablement une décision de compilation, mais le code écrit pour GC par rapport au code écrit pour NOT GC sera très différent et probablement incompatible. Comment conciliez-vous cela?

Ce ne sont que quelques-uns des problèmes rencontrés.

33
Greg Rogers

Bien qu’il s’agisse d’une question ancienne , il n’ya toujours pas de problème que je ne vois pas du tout avoir résolu: la récupération de place est presque impossible à spécifier.

En particulier, la norme C++ prend bien soin de spécifier le langage en termes de comportement observable de manière externe, plutôt que de savoir comment l'implémentation produit ce comportement. Dans le cas de la récupération de place, cependant, il n'y a pratiquement aucun comportement observable de l'extérieur.

L’idée générale du ramasse-miettes est qu’il doit faire un effort raisonnable pour s’assurer que l’allocation de mémoire réussira. Malheureusement, il est pratiquement impossible de garantir que toute allocation de mémoire réussira, même si vous avez un ramasse-miettes en opération. Ceci est vrai dans tous les cas, mais particulièrement dans le cas de C++, car il est (probablement) impossible d'utiliser un collecteur de copie (ou quelque chose de similaire) qui déplace des objets en mémoire pendant un cycle de collecte.

Si vous ne pouvez pas déplacer des objets, vous ne pouvez pas créer un seul espace mémoire contigu à partir duquel faire vos allocations - et cela signifie que votre tas (ou magasin gratuit, ou celui que vous préférez l'appeler) peut et va probablement , se fragmente avec le temps. Ceci, à son tour, peut empêcher une allocation de réussir, même s'il y a plus de mémoire disponible que la quantité demandée.

Bien qu'il soit possible de trouver certaines garanties qui indiquent (en substance) que si vous répétez exactement le même modèle d'attribution à plusieurs reprises, et que cela réussit la première fois, il continuera à réussir aux itérations suivantes, à condition que la mémoire allouée devienne inaccessible entre les itérations. C'est une garantie tellement faible que c'est essentiellement inutile, mais je ne vois aucun espoir raisonnable de la renforcer.

Néanmoins, il est plus fort que ce qui a été proposé pour C++. Le proposition précédente [avertissement: PDF] (qui a été abandonné) ne garantit rien du tout. Dans 28 pages de proposition, ce qui vous empêchait d’observer un comportement observable de l’extérieur était une simple note (non normative) disant:

[Remarque: pour les programmes ramassés de mémoire, une implémentation hébergée de haute qualité doit tenter de maximiser la quantité de mémoire inaccessible récupérée. —Fin note]

Au moins pour moi, cela soulève une question sérieuse sur le retour sur investissement. Nous allons casser le code existant (personne ne sait exactement combien, mais certainement pas mal), imposer de nouvelles exigences sur les implémentations et de nouvelles restrictions sur le code, et ce que nous obtenons en retour n'est peut-être rien du tout?

Même au mieux, nous obtenons des programmes qui, sur la base de tests avec Java , nécessiteront probablement environ six fois plus de mémoire pour s'exécuter à la même vitesse que maintenant. Pire encore, la récupération de place faisait partie de Java dès le début - C++ impose suffisamment de restrictions supplémentaires au récupérateur de place qu'il aura presque certainement un plus mauvais rapport coût/bénéfice (même si nous allons au-delà de ce que la proposition garantit et supposons qu'il y aurait un avantage).

Je résumerais la situation mathématiquement: c'est une situation complexe. Comme tout mathématicien le sait, un nombre complexe comprend deux parties: le réel et l’imaginaire. Il me semble que ce que nous avons ici, ce sont des coûts qui sont réels, mais des avantages qui sont (au moins pour la plupart) imaginaires.

21
Jerry Coffin

Si vous souhaitez la collecte automatique des déchets, il existe de bons collecteurs de déchets commerciaux et du domaine public pour C++. Pour les applications où la récupération de place convient, C++ est un excellent langage de récupération de place offrant des performances comparables à celles d'autres langages de récupération de place. Voir Langage de programmation C++ (4ème édition) pour une discussion sur la récupération de place automatique en C++. Voir aussi Hans-J. Boehm's site pour la récupération de place C et C++ ( archive ).

En outre, C++ prend en charge des techniques de programmation permettant à la gestion de la mémoire d'être sûre et implicite sans un ramasse-miettes . Je considère le ramassage des ordures comme un dernier choix et une manière imparfaite de gérer la gestion des ressources. Cela ne signifie pas que ce n'est jamais utile, mais qu'il existe de meilleures approches dans de nombreuses situations.

Source: http://www.stroustrup.com/bs_faq.html#garbage-collection

Pour ce qui est de la raison pour laquelle il n’a pas été intégré, si je me souviens bien, il a été inventé avant que GC ne soit la chose , et je ne crois pas que le langage puisse ont eu GC pour plusieurs raisons (compatibilité IE avec Backwards)

J'espère que cela t'aides.

15
Rayne

Stroustrup a fait de bons commentaires à ce sujet lors de la conférence Going Native de 2013.

Passez juste à environ 25m50 dans cette vidéo . (Je vous recommande de regarder toute la vidéo, mais ceci passe directement à la collecte de déchets.)

Lorsque vous avez un très bon langage, il est facile (et sûr, prévisible, facile à lire et facile à enseigner) de traiter les objets et les valeurs de manière directe, en évitant d’utiliser (explicitement) le tas, alors vous ne voulez même pas garbage collection.

Avec le C++ moderne et ce que nous avons dans C++ 11, la récupération de place n’est plus souhaitable, sauf dans des circonstances limitées. En fait, même si un bon ramasse-miettes est intégré à l'un des principaux compilateurs C++, je pense qu'il ne sera pas utilisé très souvent. Il sera plus facile , pas plus difficile, d’éviter le GC.

Il montre cet exemple:

void f(int n, int x) {
    Gadget *p = new Gadget{n};
    if(x<100) throw SomeException{};
    if(x<200) return;
    delete p;
}

C'est dangereux en C++. Mais c'est aussi dangereux en Java! En C++, si la fonction retourne tôt, la delete ne sera jamais appelée. Mais si vous disposiez d'une récupération de place complète, comme en Java, vous ne feriez que suggérer que l'objet serait détruit "à un moment donné dans le futur" ( Update: c'est encore pire que cela. Java ne ( ne promet pas d'appeler le finaliseur - il ne sera peut-être jamais appelé). Cela ne suffit pas si le gadget contient un descripteur de fichier ouvert, une connexion à une base de données ou des données que vous avez mises en mémoire tampon pour une écriture ultérieure dans une base de données. Nous voulons que le gadget soit détruit dès qu'il est terminé, afin de libérer ces ressources le plus rapidement possible. Vous ne voulez pas que votre serveur de base de données lutte avec des milliers de connexions de base de données qui ne sont plus nécessaires - il ne sait pas que votre programme a fini de fonctionner.

Alors, quelle est la solution? Il y a quelques approches. L'approche évidente que vous utiliserez pour la grande majorité de vos objets est la suivante:

void f(int n, int x) {
    Gadget p = {n};  // Just leave it on the stack (where it belongs!)
    if(x<100) throw SomeException{};
    if(x<200) return;
}

Cela prend moins de caractères à taper. new n'est pas gêné. Il ne vous oblige pas à taper Gadget deux fois. L'objet est détruit à la fin de la fonction. Si c'est ce que vous voulez, c'est très intuitif. Gadgets se comporte comme int ou double. Prévisible, facile à lire, facile à enseigner. Tout est une "valeur". Parfois, une grande valeur, mais les valeurs sont plus faciles à enseigner car vous n’avez pas cette "action à distance" que vous obtenez avec des pointeurs (ou des références).

La plupart des objets que vous créez sont destinés à être utilisés uniquement dans la fonction qui les a créés, et éventuellement transmis en tant qu'entrées aux fonctions enfants. Le programmeur ne devrait pas avoir à penser à la "gestion de la mémoire" lorsqu'il renvoie des objets ou ne les partage pas avec d'autres parties du logiciel.

La portée et la durée de vie sont importantes. La plupart du temps, c'est plus facile si la durée de vie est identique à la portée. C'est plus facile à comprendre et plus facile à enseigner. Quand vous voulez une durée de vie différente, il devrait être évident de lire le code, en utilisant shared_ptr par exemple. (Ou en renvoyant des objets (grands) par valeur, en exploitant la sémantique du déplacement ou unique_ptr.

Cela peut sembler un problème d'efficacité. Que se passe-t-il si je souhaite renvoyer un gadget de foo()? La sémantique de déplacement de C++ 11 facilite le retour des gros objets. Il suffit d'écrire Gadget foo() { ... } et cela fonctionnera rapidement. Vous n'avez pas besoin de jouer avec &&, il vous suffit de renvoyer les choses par valeur et le langage sera souvent capable de faire les optimisations nécessaires. (Même avant C++ 03, les compilateurs faisaient un travail remarquable pour éviter les copies inutiles.)

Comme Stroustrup l’a dit ailleurs dans la vidéo (paraphrasant): "Seul un informaticien insisterait pour copier un objet, puis pour détruire l’original. (Le public rit). Pourquoi ne pas le déplacer directement C’est ce que les humains (et non les informaticiens) attendent. "

Lorsque vous pouvez garantir qu'une seule copie d'un objet est nécessaire, il est beaucoup plus facile de comprendre la durée de vie de l'objet. Vous pouvez choisir quelle politique de durée de vie que vous voulez, et la collecte des ordures est là si vous voulez. Mais lorsque vous comprenez les avantages des autres approches, vous constaterez que la collecte des ordures est au bas de votre liste de préférences.

Si cela ne fonctionne pas pour vous, vous pouvez utiliser unique_ptr ou, à défaut, shared_ptr. Bien écrit, C++ 11 est plus court, plus facile à lire et plus facile à enseigner que beaucoup d'autres langages en matière de gestion de la mémoire.

12
Aaron McDaid

L'idée sous-jacente au C++ était que vous ne paieriez aucun impact sur les performances pour les fonctionnalités que vous n'utilisez pas. Ainsi, ajouter une récupération de place aurait signifié que certains programmes étaient exécutés directement sur le matériel, comme C et d’autres dans une sorte de machine virtuelle d’exécution.

Rien ne vous empêche d'utiliser une forme de pointeur intelligent lié à un mécanisme de récupération de place tiers. Je me souviens de me souvenir que Microsoft avait fait quelque chose comme ça avec COM et que ça n’allait pas trop bien.

10
Uri

Pour répondre à la plupart des questions "pourquoi" sur C++, lisez Conception et évolution de C++

8
Nemanja Trifunovic

Parce que le C++ moderne n'a pas besoin de récupération de place.

La FAQ --- de Bjarne Stroustrup la réponse à ce sujet dit :

Je n'aime pas les ordures. Je n'aime pas les déchets. Mon idéal est d'éliminer le besoin d'un ramasse-miettes en ne produisant aucune poubelle. C'est maintenant possible.


La situation, pour le code écrit ces jours-ci (C++ 17 et suivant le Core Guidelines ) est la suivante:

  • La plupart du code lié à la propriété de la mémoire se trouve dans les bibliothèques (en particulier celles fournissant des conteneurs).
  • La plupart utilisent du code impliquant la propriété de la mémoire suit le RAII modèle , de sorte que les affectations sont effectuées sur la construction et la désaffectation lors de la destruction, ce qui se produit lors de la sortie de la portée dans laquelle quelque chose a été alloué.
  • Vous ne pas explicitement allouer ou désallouer de la mémoire directement .
  • Pointeurs bruts ne pas posséder de mémoire (si vous avez suivi les instructions), vous ne pouvez donc pas laisser filtrer en les faisant circuler.
  • Si vous vous demandez comment vous allez passer les adresses de départ des séquences de valeurs en mémoire, vous le ferez avec un span ; pas de pointeur brut nécessaire.
  • Si vous avez vraiment besoin d'un "pointeur" propriétaire, utilisez C++ ' pointeurs intelligents de la bibliothèque standard - ils ne peuvent pas fuir et sont très efficaces. Sinon, vous pouvez transmettre la propriété à travers les limites de la portée avec "propriétaires pointeurs" . Celles-ci sont rares et doivent être utilisées explicitement. et ils permettent une vérification statique partielle contre les fuites.

"Oh oui? Mais qu'en est-il ...

... si je viens d’écrire du code comme on écrivait C++ jadis? "

En effet, vous pourriez simplement ignorer toutes les instructions et écrire du code d'application qui fuit - et il compilera et exécutera (avec une fuite), comme toujours.

Mais ce n'est pas une situation "juste ne fais pas ça", où le développeur doit être vertueux et exercer beaucoup de maîtrise de soi; ce n'est tout simplement pas plus simple d'écrire du code non conforme, ni plus rapide d'écrire, ni plus performant. Progressivement, il deviendra également plus difficile à écrire, car vous feriez face à un "décalage d'impédance" croissant avec ce que le code conforme fournit et attend.

... si je reintrepret_cast? Ou faire de l'arithmétique de pointeur? Ou d'autres hacks?

En effet, si vous y tenez, vous pouvez écrire un code qui gâche tout en jouant à Nice avec les règles. Mais:

  1. Vous le feriez rarement (en termes de place dans le code, pas nécessairement en termes de fraction de temps d'exécution)
  2. Vous ne le feriez que intentionnellement, pas accidentellement.
  3. Cela se démarquerait dans une base de code conforme aux directives.
  4. C'est le genre de code dans lequel vous éviteriez le GC dans une autre langue.

... développement de la bibliothèque? "

Si vous êtes un développeur de bibliothèque C++, vous écrivez du code non sécurisé impliquant des pointeurs bruts. Vous devez coder avec soin et de manière responsable. Cependant, il s'agit de morceaux de code autonomes écrits par des experts (et plus important encore, examinés par des experts).


C’est donc exactement ce que Bjarne a dit: Il n’ya vraiment aucune motivation pour collecter les ordures en général, mais veillez à ne pas les produire. GC devient un problème avec C++.

Cela ne veut pas dire que GC n'est pas un problème intéressant pour certaines applications spécifiques, lorsque vous souhaitez utiliser des stratégies d'allocation et de désaffectation personnalisées. Pour ceux que vous souhaitez, vous souhaitez une allocation et une désaffectation personnalisées, et non un GC de niveau linguistique.

7
einpoklum

L'un des principes fondamentaux du langage C original est que la mémoire est composée d'une séquence d'octets et que le code n'a besoin que de se soucier de ce que ces octets signifient au moment exact où ils sont utilisés. Moderne C permet aux compilateurs d'imposer des restrictions supplémentaires, mais C inclut - et C++ conserve - la possibilité de décomposer un pointeur en une séquence d'octets, d'assembler toute séquence d'octets contenant les mêmes valeurs dans un pointeur, puis d'utiliser ce pointeur pour accéder à l'objet précédent.

Bien que cette capacité puisse être utile - voire indispensable - dans certains types d’applications, un langage qui inclut cette capacité sera très limité dans sa capacité à prendre en charge tout type de récupération de place utile et fiable. Si un compilateur ne sait pas tout ce qui a été fait avec les bits qui constituent un pointeur, il n'aura aucun moyen de savoir si des informations suffisantes pour reconstruire le pointeur pourraient exister quelque part dans l'univers. Puisqu'il serait possible que ces informations soient stockées de manière que l'ordinateur ne puisse pas y accéder même s'il les connaissait (par exemple, les octets constituant le pointeur auraient pu être affichés à l'écran suffisamment longtemps pour que quelqu'un puisse les écrire) sur un morceau de papier), il peut être littéralement impossible à un ordinateur de savoir si un pointeur pourrait éventuellement être utilisé à l’avenir.

Une bizarrerie intéressante de nombreux frameworks ramassés par les déchets est qu’une référence d’objet n’est pas définie par les modèles de bits qu’elle contient, mais par la relation entre les bits contenus dans la référence d’objet et d’autres informations conservées ailleurs. En C et C++, si le motif de bits stocké dans un pointeur identifie un objet, ce motif de bits identifiera cet objet jusqu'à ce que l'objet soit explicitement détruit. Dans un système GC typique, un objet peut être représenté par un motif binaire 0x1234ABCD à un moment donné, mais le cycle GC suivant peut remplacer toutes les références à 0x1234ABCD par des références à 0x4321BABE, l'objet étant alors représenté par ce dernier modèle. Même si l'on devait afficher le motif de bits associé à une référence d'objet, puis le relire ensuite sur le clavier, il n'y aurait aucune attente que le même motif de bits soit utilisable pour identifier le même objet (ou n'importe quel objet).

4
supercat

Tous les discours techniques compliquent le concept.

Si vous mettez automatiquement GC en C++ pour toute la mémoire, envisagez quelque chose comme un navigateur Web. Le navigateur Web doit charger un document Web complet ET exécuter des scripts Web. Vous pouvez stocker des variables de script Web dans l'arborescence du document. Dans un document BIG dans un navigateur avec de nombreux onglets ouverts, cela signifie que chaque fois que le GC doit effectuer une collection complète, il doit également analyser tous les éléments du document.

Sur la plupart des ordinateurs, cela signifie que des défauts de page se produiront. Donc, la raison principale pour répondre à la question est que les défauts de page se produiront. Vous le saurez dès que votre PC commencera à créer de nombreux accès au disque. En effet, le CPG doit toucher beaucoup de mémoire pour prouver des pointeurs invalides. Lorsque vous utilisez une application réelle qui utilise beaucoup de mémoire, le fait d'analyser tous les objets de chaque collection est un véritable chaos en raison des défauts de page. Une erreur de page survient lorsque la mémoire virtuelle doit être relue dans le disque RAM.

La solution correcte consiste donc à diviser une application en parties nécessitant le GC et celles ne le faisant pas. Dans l'exemple de navigateur Web ci-dessus, si l'arborescence de documents a été allouée avec malloc, mais que javascript a été exécuté avec GC, chaque fois que le GC le lancera, seule une petite partie de la mémoire et tous les éléments PAGED OUT de la mémoire seront analysés. l’arborescence de documents n’a pas besoin d’être retrouvée.

Pour mieux comprendre ce problème, consultez la mémoire virtuelle et son implémentation dans les ordinateurs. Tout dépend du fait que 2 Go sont disponibles pour le programme quand il n’ya pas vraiment beaucoup de RAM. Sur les ordinateurs modernes dotés de 2 Go RAM pour un système 32BIt, le problème ne se pose pas tant qu'un seul programme est en cours d'exécution.

A titre d'exemple supplémentaire, considérons une collection complète qui doit suivre tous les objets. Tout d'abord, vous devez analyser tous les objets accessibles via les racines. Deuxièmement, analysez tous les objets visibles à l’étape 1. Ensuite, analysez les destructeurs en attente. Ensuite, accédez à nouveau à toutes les pages et désactivez tous les objets invisibles. Cela signifie que de nombreuses pages peuvent être échangées plusieurs fois.

Donc, ma réponse pour résumer, c'est que le nombre de défauts de page provoqués par le toucher à toute la mémoire rend impossible le remplissage complet de la mémoire pour tous les objets d'un programme. Le programmeur doit donc considérer le GC comme une aide pour des scripts. et base de données fonctionnent, mais font des choses normales avec la gestion manuelle de la mémoire.

Et l’autre raison très importante est bien sûr les variables globales. Pour que le collecteur sache qu'un pointeur de variable globale se trouve dans le CPG, des mots-clés spécifiques seraient nécessaires. Par conséquent, le code C++ existant ne fonctionnerait pas.

3
Bob Holmes

RÉPONSE COURTE: Nous ne savons pas comment effectuer le ramassage des ordures de manière efficace (avec des frais mineurs en temps et en espace) et correctement tout le temps (dans tous les cas possibles).

RÉPONSE LONGUE: Tout comme C, C++ est un langage système; cela signifie qu'il est utilisé lorsque vous écrivez du code système, par exemple un système d'exploitation. En d'autres termes, le C++ est conçu, tout comme le C, avec les meilleures performances possibles comme cible principale. Le langage standard n'apportera aucune fonctionnalité susceptible de nuire à l'objectif de performance.

Cela met en pause la question: Pourquoi la récupération de place nuit-elle aux performances? La raison principale en est que, en ce qui concerne la mise en œuvre, nous [les informaticiens] ne savons pas comment procéder au ramassage des ordures avec un temps système minimal, dans tous les cas. Par conséquent, il est impossible au compilateur C++ et au système d'exécution d'effectuer efficacement le nettoyage de place. D'autre part, un programmeur C++ devrait connaître sa conception/implémentation et il est le mieux placé pour décider de la meilleure façon de faire la collecte des ordures.

Enfin, si le contrôle (matériel, détails, etc.) et les performances (temps, espace, puissance, etc.) ne sont pas les principales contraintes, C++ n'est pas l'outil d'écriture. D'autres langues pourraient mieux servir et offrir plus de gestion d'exécution [cachée], avec les frais généraux nécessaires.

3
Sqandr

Lorsque vous comparez C++ à Java, vous voyez immédiatement que C++ n'a pas été conçu avec une récupération de place implicite, contrairement à Java.

Disposer d'éléments tels que des pointeurs arbitraires dans C-Style et des destructeurs déterministes ralentit non seulement les performances des implémentations GC, mais détruit également la compatibilité avec les versions antérieures pour une grande quantité de code hérité C++.

En plus de cela, C++ est un langage destiné à être exécuté en tant qu'exécutable autonome au lieu de disposer d'un environnement d'exécution complexe.

Toutes choses considérées: Oui, il serait possible d’ajouter Garbage Collection au C++, mais dans un souci de continuité, il est préférable de ne pas le faire. Cela coûterait plus cher que le bénéfice.

3
Mike76

Imposer un ramassage des ordures représente un changement de paradigme de bas niveau à un niveau élevé.

Si vous examinez la façon dont les chaînes sont gérées dans une langue avec garbage collection, vous constaterez qu'elles autorisent UNIQUEMENT les fonctions de manipulation de chaîne de haut niveau et n'autorisent pas l'accès binaire aux chaînes. En termes simples, toutes les fonctions de chaîne vérifient d’abord les pointeurs pour voir où se trouve la chaîne, même si vous ne dessinez qu’un octet. Ainsi, si vous effectuez une boucle qui traite chaque octet d'une chaîne dans un langage avec garbage collection, elle doit calculer l'emplacement de base plus le décalage pour chaque itération, car elle ne peut pas savoir quand la chaîne a été déplacée. Ensuite, vous devez penser aux tas, aux piles, aux threads, etc.

0
www-0av-Com

Principalement pour deux raisons:

  1. Parce qu'il n'en a pas besoin (IMHO)
  2. Parce que c'est assez incompatible avec RAII, qui est la pierre angulaire du C++

C++ offre déjà la gestion manuelle de la mémoire, l'allocation de pile, la RAII, les conteneurs, les pointeurs automatiques, les pointeurs intelligents ... Cela devrait suffire. Les éboueurs sont destinés aux programmeurs paresseux qui ne veulent pas passer 5 minutes à se demander qui doit posséder quels objets ou quand les ressources doivent être libérées. Ce n'est pas comme ça qu'on fait les choses en C++.

0
Marc Coll