Question

Je ne pose pas cette question en raison des mérites de la collecte des ordures ménagères. 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 ++. N’est-ce qu’un de ceux-ci "plus facile à dire qu'à faire" tapez des choses? Ou existe-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é.

Était-ce utile?

La solution

La récupération de place implicite 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.

Citation de Bjarne Stroustrup lui-même:

  

J'espérais qu'un éboueur   qui pourrait éventuellement être activé   ferait partie de C ++ 0x, mais il y avait   assez de problèmes techniques que j'ai   se contenter d'un détail   spécification de la façon dont un tel collecteur   intègre avec le reste de la   langue, si fournie. Comme c'est le cas   avec essentiellement toutes les fonctionnalités C ++ 0x,   une implémentation expérimentale existe.

Il existe une bonne discussion sur le sujet ici .

Présentation générale:

C ++ est très puissant et vous permet de faire presque n'importe quoi. Pour cette raison, il n’exercera pas automatiquement beaucoup d’effets 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 compte de référence, 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 récupération de place. L’efficacité était la principale préoccupation à laquelle le C ++ devait faire face, comparé à C et d’autres.

Il existe 2 types de récupération de place ...

Collecte de place explicite:

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

Si vous le souhaitez, vous pouvez l'utiliser, sinon vous n'êtes pas obligé de l'utiliser.

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

Collecte de place implicite:

Cependant, il n’a pas de récupération de place transparente 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é.

Autres conseils

À 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'y en a 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:

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

Le Marquer et balayer est plus rapide (son impact sur les performances globales est moindre), mais il souffre du "gel du monde". syndrom: c'est-à-dire que lorsque le GC entre en action, tout le reste est arrêté jusqu'à ce que le GC ait terminé son nettoyage. Si vous souhaitez créer un serveur qui réponde en quelques millisecondes, certaines transactions ne seront pas à la hauteur de vos attentes:)

Le problème de Comptage de références est différent: le comptage de références ajoute une surcharge, en particulier dans les environnements multi-threading, car vous devez disposer d'un compte atomique. De plus, il y a le problème des cycles de référence, vous avez donc besoin d'un algorithme intelligent pour détecter ces cycles et les éliminer (généralement implémenté par un "gel du monde" également, 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 eu connaissance d'un document rédigé par des développeurs Eiffel qui tentaient d'implémenter un collecteur de déchets Reference Counting qui aurait une performance globale similaire à Mark And Sweep sans le " ; Geler le monde " aspect. Il fallait un thread séparé pour le CPG (typique). L’algorithme était un peu effrayant (à la fin), mais le document faisait un bon travail en introduisant les concepts un par un et en montrant l’évolution de l’algorithme à partir du "simple" version à la version complète. Lecture recommandée si seulement je pouvais remettre mes mains sur le fichier PDF ...

2. L’acquisition de ressources est une initialisation

C’est un idiome courant dans C ++ que vous allez encapsuler la propriété des ressources dans un objet pour vous assurer qu’elles sont correctement publié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 aurez besoin
  • il devrait être tué quand vous en aurez fini

Le problème de la GC est que, si cela aide avec l’ancien et finalement garantit que plus tard ... ce "ultime" peut ne pas être suffisant. Si vous relâchez 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 l'étendue définit la durée de vie
  • utilisant 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 en utilisant )

3. Pointeurs intelligents

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 pourrait pas être plus faux.

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 entre 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 cela que Boost est utile, mais ce n’est pas une solution miracle. En fait, le principal problème de shared_ptr est qu'il émule un GC implémenté par Reference Counting , mais que vous devez implémenter la détection de cycle par vous-même ... Urg

Bien sûr, il y a cette chose faible_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 quand vous êtes dans un environnement multi-thread, il est extrêmement difficile à détecter!

4. Quelle est la solution?

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

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

Donc, en effet, ce serait formidable d'avoir un GC ... Cependant, ce n'est pas une question triviale. Et dans le même temps, nous avons juste besoin de nous retrousser les manches.

Quel type? doit-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 donner 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 un trop grand nombre de circonstances différentes. Je soupçonne que quelque chose comme des pointeurs intelligents boost sera suffisant pour la plupart des utilisateurs

Modifier - Les récupérateurs de mémoire automatiques ne posent pas vraiment de problème de performances (vous pouvez toujours acheter plus de serveur), il s’agit d’une question de performances prévisibles.
Ne pas savoir quand le GC va entrer en jeu revient à employer un pilote de ligne aérienne narcoleptique, ils sont généralement géniaux - mais lorsque vous avez vraiment besoin de réactivité!

L'une des principales raisons pour lesquelles C ++ n'est pas intégré dans la récupération de place est qu'il est très difficile d'obtenir de cette manière une récupération de mémoire avec les destructeurs. Pour autant que je sache, personne ne sait vraiment comment le résoudre complètement. Il y a beaucoup de problèmes à traiter:

  • La durée de vie déterministe des objets (le comptage de références vous en donne, mais pas le GC. Bien que cela ne soit pas forcément un gros problème).
  • que se passe-t-il si un destructeur jette quand l'objet est en train d'être ramassé? La plupart des langages ignorent cette exception, car il n’ya 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 réconciliez-vous cela?

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

Bien qu’il s’agisse d’une vieille question, il reste un problème que je ne vois pas qui que ce soit qui l’ait abordée: la récupération de place est presque impossible à spécifier.

En particulier, le standard C ++ est très prudent en spécifiant le langage en termes de comportement observable de manière externe, plutôt qu'en fonction de la manière dont l'implémentation parvient à 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 de la récupération de place est qu’elle 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. C’est vrai dans tous les cas, en particulier dans le cas de C ++, car il n’est (probablement) pas possible d’utiliser un collecteur de copie (ou quelque chose de similaire) qui déplace des objets en mémoire au cours d’un cycle de collecte.

Si vous ne pouvez pas déplacer les objets, vous ne pouvez pas créer un seul espace mémoire contigu à partir duquel vous pouvez effectuer vos allocations - ce qui signifie que votre segment de mémoire (ou magasin gratuit, ou celui que vous préférez l'appeler) peut: et va probablement se fragmenter avec le temps. Ceci, à son tour, peut empêcher la réussite d’une allocation, même s’il ya plus de mémoire disponible que la quantité demandée.

Bien qu'il soit possible de trouver une garantie, cela indique (en substance) que si vous répétez exactement le même schéma d'allocation de manière répétée, et que cela a réussi la première fois, cela continuera. réussir les 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.

Même dans ce cas, il est plus fort que ce qui a été proposé pour C ++. La proposition précédente [avertissement: PDF] (qui a été abandonné) ne garantit rien du tout. Dans une proposition de 28 pages, vous remarquiez un comportement observable de l’extérieur par une simple note (non normative) disant:

  

[Remarque: pour les programmes collectés avec ordures, 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]

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

Même au mieux, nous obtenons des programmes basés sur les tests avec Java nécessiteront probablement environ six fois plus de mémoire pour fonctionner à la même vitesse qu’aujourd’hui. Pire encore, la récupération de place faisait partie de Java depuis le début - le C ++ impose suffisamment de restrictions au ramasse-miettes pour qu’il ait presque certainement un rapport coût / bénéfice encore pire (même si nous allons au-delà du proposition garantie et supposons qu'il y aurait un avantage).

Je résumerais la situation mathématiquement: il s’agit d’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.

  

Si vous souhaitez un ramassage automatique des ordures, il existe de bons outils commerciaux.   et les éboueurs du domaine public pour C ++. Pour les applications où   le ramassage des ordures convient, C ++ est un excellent ramassage des ordures   langue avec une performance qui se compare favorablement avec d'autres ordures   langues collectées. Voir le langage de programmation C ++ (quatrième   Edition) pour une discussion sur la récupération de place automatique en C ++.   Voir aussi Hans-J. Le site de Boehm pour le nettoyage des ordures C et C ++ ( archive ).

     

De plus, C ++ prend en charge les techniques de programmation permettant la mémoire   la direction doit être sûre et implicite sans collecteur de déchets . Je considère la collecte 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 ne l’a pas intégré, si je me souviens bien, il a été inventé avant que GC ne soit ce qui , et je ne crois pas que la langue aurait pu utiliser GC pour plusieurs raisons (IE Compatibilité avec C)

J'espère que cela vous aidera.

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

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

Lorsque vous avez un très bon langage qui rend facile (et sûr, et prévisible, et facile à lire et à enseigner) de traiter des objets et des valeurs de manière directe, en évitant (explicite) utilisation du tas, alors vous ne même pas vouloir une poubelle.

Avec le C ++ moderne et ce que nous avons dans C ++ 11, le ramassage des ordures ménagères 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, le delete ne sera jamais appelé. Mais si vous avez une récupération complète des déchets, comme en Java, vous obtenez simplement une suggestion selon laquelle l'objet sera détruit "à un moment donné dans le futur". ( Mise à jour: c'est encore pire que cela. Java ne ne fasse 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é par cela. 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. Les gadgets se comportent de la même manière que 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" lors du renvoi d’objets ou du partage d’objets entre des parties largement séparées 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 que vous faites cela, en utilisant par exemple shared_ptr . (Ou en renvoyant des objets (grands) par valeur, en utilisant move-sémantique ou unique_ptr .

Cela peut sembler être un problème d’efficacité. Que faire si je veux retourner un gadget à partir de foo () ? La sémantique de déplacement de C ++ 11 facilite le retour des gros objets. Il suffit d’écrire Gadget foo () {...} pour que cela fonctionne, et fonctionne rapidement. Vous n'avez pas besoin de manipuler & amp; & amp; vous-même, il vous suffit de renvoyer les éléments par valeur et la langue sera souvent en mesure d'effectuer les optimisations nécessaires. (Même avant C ++ 03, les compilateurs faisaient un travail remarquable pour éviter les copies inutiles.)

Comme le dit Stroustrup ailleurs dans la vidéo (paraphrasant): "Seul un informaticien insisterait pour copier un objet, puis pour détruire l'original. (l'auditoire rit) Pourquoi ne pas simplement déplacer l'objet directement vers le nouvel emplacement? C’est ce à quoi les humains (et non les informaticiens) s’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 langues en matière de gestion de la mémoire.

L’idée sous-jacente du 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 des pointeurs intelligents liés à 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.

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

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

La FAQ de Bjarne Stroustrup, , indique :

  

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 texte officiel Principes directeurs ) est la suivante:

  • La plupart du code relatif à la propriété de la mémoire se trouve dans des bibliothèques (en particulier celles fournissant des conteneurs).
  • La plupart des utilisations du code impliquant la propriété de la mémoire suivent le code RAII pattern . Par conséquent, l’attribution est effectuée lors de la construction et la désaffectation lors de la destruction, ce qui se produit lors de la sortie du périmètre dans lequel un élément a été alloué.
  • Vous n'allouez pas explicitement ni ne désallouez la mémoire directement .
  • Pointeurs bruts ne pas posséder de mémoire ( si vous avez suivi les consignes), vous ne pouvez donc pas fuir 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 ; aucun pointeur brut nécessaire.
  • Si vous avez vraiment besoin d'un "pointeur" propriétaire, utilisez C ++ ' pointeurs intelligentes de la bibliothèque standard - elles ne peuvent pas fuir et sont très efficaces. Vous pouvez également transférer la propriété à travers les limites de la portée avec " pointeurs de propriétaire " ; . Celles-ci sont rares et doivent être utilisées explicitement. et ils permettent une vérification statique partielle des fuites.

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

... si j'écrivais simplement le code de la même manière que nous utilisions C ++ à l'époque? "

En effet, vous pouvez simplement ignorer toutes les directives et écrire du code d'application qui présente des fuites - et il compilera et exécutera (avec des fuites), comme d'habitude.

Mais ce n'est pas un "ne faites pas ça" situation, 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éséquilibre 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 piratages de ce type? "

En effet, si vous y tenez, vous pouvez écrire un code qui gâche tout en jouant bien avec les instructions. 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 et non accidentellement.
  3. Cela ressortira 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èques C ++, vous écrivez un code non sécurisé impliquant des pointeurs bruts. Vous devez coder avec soin et responsabilité. Cependant, il s'agit de morceaux de code autonomes écrits par des experts (et plus important encore, examinés par experts).


Donc, c'est comme Bjarne l'a dit: Il n'y a vraiment aucune motivation à collecter les ordures en général, mais assurez-vous de ne pas produire d'ordures. GC ne devient plus 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 qui souhaiteraient une allocation et une désaffectation personnalisées, pas un CPG de niveau linguistique.

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 s’intéresser à la signification de ces octets 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 incluant 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 particularité intéressante de nombreux frameworks ramassés par les déchets est qu’une référence à un 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 de l’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 affichait le motif de bits associé à une référence d'objet et que l'on le lirait ensuite à partir du clavier, on ne s'attendrait pas à ce que le même motif de bits soit utilisable pour identifier le même objet (ou n'importe quel objet).

Tous les discours techniques compliquent trop 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 se produit lorsque la mémoire virtuelle doit être lue dans la RAM à partir du disque.

La solution correcte consiste donc à diviser une application en deux parties: celles qui nécessitent GC et celles qui ne le sont 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. il n’est pas nécessaire que l’arborescence du document soit renvoyé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 de RAM pour un système 32BIt, ce n’est pas un problème si un seul programme est en cours d’exécution.

Comme 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 est simple: 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 voir le fonctionnement de GC comme une aide à la résolution. des choses comme les scripts et les bases de données fonctionnent, mais faites des choses normales avec la gestion manuelle de la mémoire.

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 GC, il faudrait des mots-clés spécifiques. Par conséquent, le code C ++ existant ne fonctionnerait pas.

REPONSE COURTE: Nous ne savons pas comment procéder à la collecte des ordures de manière efficace (avec du temps et des frais d'espace minimes) 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 la meilleure performance possible comme cible principale. Le langage standard n'apportera aucune fonctionnalité susceptible de nuire à l'objectif de performance.

Ceci 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 à la collecte des ordures avec une surcharge minimale, 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 récupérer les déchets.

Enfin, si le contrôle (matériel, détails, etc.) et les performances (temps, espace, alimentation, 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.

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 ++.

De plus, C ++ est un langage conçu pour être exécuté en tant qu'exécutable autonome au lieu de disposer d'un environnement d'exécution complexe.

Tout compte fait: Oui, il serait possible d’ajouter Garbage Collection à C ++, mais par souci de continuité, il est préférable de ne pas le faire. Cela coûterait plus cher que l'avantage.

Principalement pour deux raisons:

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

C ++ offre déjà la gestion manuelle de la mémoire, l'allocation de pile, 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 devrait posséder quels objets ou quand les ressources devraient être libérées. Ce n'est pas comme ça qu'on fait les choses en C ++.

Imposer un ramasse-miettes est vraiment un changement de paradigme de bas niveau à un niveau élevé.

Si vous examinez la façon dont les chaînes sont traitées dans une langue avec garbage collection, vous constaterez qu’elles autorisent UNIQUEMENT les fonctions de manipulation de chaîne de haut niveau et ne permettent pas un 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., etc.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top