Question

Je n'arrête pas d'entendre les gens se plaindre que C ++ n'a pas de récupération de place. J'ai aussi entendu dire que le Comité de normalisation C ++ envisageait de l'ajouter au langage. J'ai bien peur de ne pas voir le but. Utiliser RAII avec des pointeurs intelligents en élimine le besoin, non?

Ma seule expérience en matière de ramassage des ordures a été réalisée sur deux ordinateurs domestiques bon marché des années 80, où cela signifiait que le système se figeait quelques secondes de temps en temps. Je suis sûr que la situation s’est améliorée depuis, mais comme vous pouvez le deviner, cela ne m’a pas laissé une grande opinion.

Quels avantages la récupération de place peut-elle offrir à un développeur C ++ expérimenté?

Était-ce utile?

La solution

Je n'arrête pas d'entendre les gens se plaindre que C ++ n'a pas de récupération de place.

Je suis tellement désolé pour eux. Sérieusement.

C ++ a RAII et je me plains toujours de ne trouver aucun RAII (ou un RAII castré) dans les langages Garbage Collected.

Quels avantages la récupération de place peut-elle offrir à un développeur C ++ expérimenté?

Un autre outil.

Matt J l’a bien écrit dans son message ( Récupération des ordures en C ++ - pourquoi ? ): Nous n'avons pas besoin des fonctionnalités C ++, car la plupart d'entre elles pourraient être codées en C, et nous n'avons pas besoin de fonctionnalités C, car la plupart d'entre elles pourraient être codées dans Assembly, etc. Le C ++ doit évoluer .

En tant que développeur: je me fiche de GC. J'ai essayé à la fois RAII et GC, et je trouve que RAII est nettement supérieur. Comme l'a dit Greg Rogers dans son message ( Collecte des ordures en C ++ - pourquoi? ), les fuites de mémoire ne sont pas si terribles (du moins en C ++, où elles sont rares si le C ++ est réellement utilisé) que de justifier le GC au lieu de RAII. GC propose une désallocation / finalisation non déterministe et constitue simplement un moyen d'écrire un code qui ne tient à rien avec des choix de mémoire spécifiques .

Cette dernière phrase est importante: il est important d’écrire du code qui "ne s’inquiète pas". De la même manière, en C ++ RAII, nous ne nous soucions pas de libérer des ressources car RAII le fait pour nous, ou pour l'initialisation d'un objet parce que le constructeur le fait pour nous, il est parfois important de simplement coder sans se soucier de qui est propriétaire de quelle mémoire, et quel type de pointeur (partagé, faible, etc.) nous avons besoin pour ceci ou pour ce morceau de code. Il semble y avoir un besoin de GC en C ++. (même si je ne parviens pas à le voir personnellement)

Exemple d'utilisation judicieuse de GC en C ++

Parfois, dans une application, vous avez des "données flottantes". Imaginez une structure de données en forme d’arbre, mais personne n’est vraiment "propriétaire" des données (et personne ne se soucie vraiment de savoir quand exactement elles seront détruites). Plusieurs objets peuvent l'utiliser, puis le rejeter. Vous voulez qu'il soit libéré lorsque personne ne l'utilise plus.

L’approche C ++ utilise un pointeur intelligent. Le boost :: shared_ptr vient à l’esprit. Chaque donnée appartient donc à son propre pointeur partagé. Cool. Le problème est que chaque donnée peut faire référence à une autre donnée. Vous ne pouvez pas utiliser de pointeurs partagés, car ils utilisent un compteur de références, qui ne prend pas en charge les références circulaires (A pointe sur B et B pointe sur A). Vous devez donc beaucoup savoir où utiliser les pointeurs faibles (boost :: faible_ptr) et quand utiliser des pointeurs partagés.

Avec un CPG, il vous suffit d'utiliser les données structurées en arborescence.

L'inconvénient est que vous ne devez pas vous soucier lorsque les "données flottantes". sera vraiment détruit. Seul le sera détruit.

Conclusion

Donc, au final, si cela est fait correctement et compatible avec les idiomes actuels du C ++, GC serait un Encore un autre bon outil pour C ++ .

C ++ est un langage multiparadigmatique: ajouter un GC fera peut-être pleurer certains fans de C ++ à cause de la trahison, mais au final, cela pourrait être une bonne idée, et je suppose que le Comité de normalisation C ++ ne laissera pas ce genre de problème majeur. les fonctionnalités cassent le langage, nous pouvons donc leur faire confiance pour effectuer le travail nécessaire à l'activation d'un GC C ++ correct qui n'interférera pas avec C ++: Comme toujours en C ++, si vous n'avez pas besoin d'une fonctionnalité, n'utilisez pas et cela ne vous coûtera rien.

Autres conseils

La réponse courte est que la récupération de place est très similaire en principe à RAII avec des pointeurs intelligents. Si chaque pièce de mémoire que vous allouez se trouve dans un objet et que cet objet est uniquement référencé par des pointeurs intelligents, vous disposez d'un élément proche de la récupération de place (potentiellement meilleur). L’avantage de ne pas avoir à être aussi judicieux sur la portée et le pointage intelligent de chaque objet et à laisser le moteur d’exécution faire le travail à votre place.

Cette question semble analogue à "Qu'est-ce que C ++ a à offrir au développeur d'assemblage expérimenté? instructions et sous-programmes en éliminent le besoin, non? "

Avec l'apparition de bons vérificateurs de mémoire comme Valgrind, je ne vois pas l'utilité de la collecte des ordures en tant que filet de sécurité "dans le cas où" nous avons oublié de désallouer quelque chose - d’autant plus que cela n’aide pas beaucoup à gérer le cas plus générique de ressources autres que la mémoire (bien que celles-ci soient beaucoup moins courantes). En outre, allouer et désallouer explicitement de la mémoire (même avec des pointeurs intelligents) est assez rare dans le code que j'ai vu, car les conteneurs sont généralement un moyen beaucoup plus simple et meilleur.

Toutefois, la récupération de place peut offrir des avantages en termes de performances, en particulier si de nombreux objets de courte durée sont alloués en tas. GC offre également potentiellement une meilleure localité de référence pour les objets nouvellement créés (comparables aux objets de la pile).

La programmation lambda, les fonctions anonymes, etc. semblent être le facteur déterminant de la prise en charge du GC en C ++. Il s'avère que les bibliothèques lambda bénéficient de la possibilité d'allouer de la mémoire sans se soucier du nettoyage. L'avantage pour les développeurs ordinaires serait une compilation plus simple des bibliothèques lambda, plus fiable et plus rapide.

GC aide également à simuler une mémoire infinie; la seule raison pour laquelle vous devez supprimer les POD est que vous devez recycler la mémoire. Si vous avez une mémoire GC ou une mémoire infinie, il n'est plus nécessaire de supprimer les POD.

Le comité n’ajoute pas la récupération de place, il ajoute quelques fonctionnalités permettant une mise en œuvre plus sûre de la récupération de place. Seul le temps nous dira s’ils ont réellement un effet sur les futurs compilateurs. Les implémentations spécifiques peuvent varier considérablement, mais impliqueront très probablement une collecte basée sur l’atteignabilité, ce qui pourrait impliquer un léger blocage, en fonction de la méthode utilisée.

Une chose est, cependant, aucun récupérateur de déchets conforme aux normes ne pourra appeler des destructeurs - seulement pour réutiliser silencieusement la mémoire perdue.

Quels avantages la récupération de place peut-elle offrir à un développeur C ++ expérimenté?

Ne pas avoir à rechercher les fuites de ressources dans le code de vos collègues moins expérimentés.

Je ne comprends pas comment on peut affirmer que RAII remplace GC ou est considérablement supérieur. Il existe de nombreux cas traités par un gc que la RAII ne peut tout simplement pas traiter du tout. Ce sont des bêtes différentes.

Tout d’abord, RAII n’est pas infaillible: il va à l’encontre de certains échecs courants omniprésents en C ++, mais il existe de nombreux cas où RAII n’aide en rien; il est fragile aux événements asynchrones (comme les signaux sous UNIX). Fondamentalement, RAII s'appuie sur la portée: lorsqu'une variable est hors de portée, elle est automatiquement libérée (en supposant que le destructeur soit correctement implémenté).

Voici un exemple simple où ni auto_ptr ni RAII ne peuvent vous aider:

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <memory>

using namespace std;

volatile sig_atomic_t got_sigint = 0;

class A {
        public:
                A() { printf("ctor\n"); };
                ~A() { printf("dtor\n"); };
};

void catch_sigint (int sig)
{
        got_sigint = 1;
}

/* Emulate expensive computation */
void do_something()
{
        sleep(3);
}

void handle_sigint()
{
        printf("Caught SIGINT\n");
        exit(EXIT_FAILURE);
}

int main (void)
{
        A a;
        auto_ptr<A> aa(new A);

        signal(SIGINT, catch_sigint);

        while (1) {
                if (got_sigint == 0) {
                        do_something();
                } else {
                        handle_sigint();
                        return -1;
                }
        }
}

Le destructeur de A ne sera jamais appelé. Bien sûr, il s’agit d’un exemple artificiel et quelque peu artificiel, mais une situation similaire peut en réalité se produire; Par exemple, lorsque votre code est appelé par un autre code qui gère SIGINT et sur lequel vous n'avez aucun contrôle (exemple concret: extensions mex dans matlab). C'est la même raison pour laquelle finalement en python ne garantit pas l'exécution de quelque chose. Gc peut vous aider dans ce cas.

Les autres idiomes ne fonctionnent pas bien avec ceci: dans tout programme non trivial, vous aurez besoin d’objets avec état (j’utilise le mot objet dans un sens très large ici, cela peut être n'importe quelle construction permise par le langage); si vous devez contrôler l'état en dehors d'une fonction, vous ne pouvez pas le faire facilement avec RAII (c'est pourquoi RAII n'est pas très utile pour la programmation asynchrone). OTOH, gc a une vue de l’ensemble de la mémoire de votre processus, c’est-à-dire qu’il connaît tous les objets qu’il a alloué et qu’il peut être nettoyé de manière asynchrone.

Il peut également être beaucoup plus rapide d’utiliser gc, pour les mêmes raisons: si vous devez allouer / désallouer de nombreux objets (en particulier de petits objets), gc surperformera considérablement RAII, à moins que vous n'écriviez un allocateur personnalisé, car gc peut allouer / nettoyer de nombreux objets en une seule passe. Certains projets C ++ bien connus utilisent gc, même lorsque les performances importent (voir par exemple Tim Sweenie à propos de l'utilisation de gc dans Unreal Tournament: http://lambda-the-ultimate.org/node/1277 ). GC augmente essentiellement le débit au prix de la latence.

Bien sûr, il existe des cas où RAII est meilleur que gc; en particulier, le concept gc concerne principalement la mémoire, et ce n'est pas la seule ressource. Des choses comme les fichiers, etc. peuvent être bien gérées avec RAII. Les langages sans gestion de la mémoire, comme python ou ruby, ont quelque chose comme RAII pour ces cas, BTW (avec une instruction en python). RAII est très utile lorsque vous devez contrôler précisément le moment où la ressource est libérée, ce qui est souvent le cas pour les fichiers ou les verrous, par exemple.

C’est une erreur banale de supposer que, du fait que C ++ n’a pas de garbage collection cuit dans le langage , vous ne pouvez pas utiliser de garbage collection dans une période C ++. Ça n'a pas de sens. Je connais des programmeurs C ++ d’élite qui utilisent systématiquement le collecteur Boehm dans leur travail.

La

récupération de place permet de reporter la décision relative à l'identité de propriétaire de un objet.

C ++ utilise la sémantique des valeurs. Ainsi, avec RAII, les objets sont rappelés lorsqu'ils sortent de la portée. Ceci est parfois appelé "GC immédiat".

Lorsque votre programme commence à utiliser la sémantique de référence (via des pointeurs intelligents, etc.), le langage ne vous prend plus en charge, vous êtes alors laissé à l'esprit de votre bibliothèque de pointeurs intelligents.

La difficulté avec GC est de décider quand un objet n'est plus nécessaire.

La récupération de place rend la UCR parfaitement plus facile à mettre en œuvre correctement et efficacement.

Sécurité de thread et évolutivité plus faciles

Il existe une propriété de GC qui peut être très importante dans certains scénarios. L'attribution de pointeur est naturellement atomique sur la plupart des plates-formes, tandis que la création de pointeurs comptés de références ("intelligentes") sécurisées par les threads est assez difficile et introduit un temps système de synchronisation important. En conséquence, les pointeurs intelligents se font souvent dire "de ne pas bien s'adapter" sur une architecture multi-core.

La récupération de place est vraiment la base de la gestion automatique des ressources. Et avoir GC change la façon dont vous abordez les problèmes d’une manière difficile à quantifier. Par exemple, lorsque vous effectuez une gestion manuelle des ressources, vous devez:

  • Déterminez quand un élément peut être libéré (tous les modules / classes en ont-ils fini?
  • Déterminez qui est responsable de la libération d'une ressource lorsqu'elle est prête à être libérée (quelle classe / quel module doit libérer cet élément?)

Dans le cas trivial, il n'y a pas de complexité. Par exemple. vous ouvrez un fichier au début d'une méthode et le fermez à la fin. Ou l'appelant doit libérer ce bloc de mémoire renvoyé.

Les choses commencent à se compliquer rapidement lorsque plusieurs modules interagissent avec une ressource et que les personnes à nettoyer ne sont pas aussi claires. Le résultat final est que toute l'approche utilisée pour résoudre un problème inclut certains modèles de programmation et de conception qui constituent un compromis.

Dans les langues avec nettoyage de la mémoire, vous pouvez utiliser un disponible

Les pointeurs intelligents qui sont en fait un exemple parfait des compromis que j'ai mentionnés. Les pointeurs intelligents ne peuvent vous éviter des fuites de structures de données cycliques sans un mécanisme de sauvegarde. Pour éviter ce problème, vous devez souvent faire des compromis et éviter d'utiliser une structure cyclique, même si ce n'est pas la meilleure solution.

Moi aussi, j'ai des doutes sur le fait que le comité C ++ ajoute une collection de déchets complète au standard.

Mais je dirais que la raison principale pour ajouter / avoir un ramassage des ordures en langage moderne est qu’il existe trop peu de bonnes raisons pour contre le ramassage des ordures. Depuis les années quatre-vingt, plusieurs avancées considérables ont été réalisées dans le domaine de la gestion de la mémoire et du ramassage des ordures, et je crois qu’il existe même des stratégies de ramassage des ordures qui pourraient vous donner des garanties en temps réel (comme, par exemple, "GC ne prendra pas plus que ... dans le pire des cas ").

  

utiliser RAII avec des pointeurs intelligents en élimine le besoin, non?

Les pointeurs intelligents peuvent être utilisés pour implémenter le comptage de références en C ++, qui est une forme de garbage collection (gestion automatique de la mémoire), mais les CPG de production n'utilisent plus le comptage de références car il présente des lacunes importantes:

  1. Référence comptage des cycles de fuites. Considérez A?B, les objets A et B se référant l'un à l'autre, ils ont donc un compte de référence de 1 et aucun des deux n'est collecté, mais ils doivent tous deux être récupérés. Des algorithmes avancés tels que la suppression de la version d'évaluation résolvent ce problème mais en ajoutent beaucoup de complexité. Utiliser faible_ptr comme solution de contournement revient à la gestion manuelle de la mémoire.

  2. Le comptage naïf des références est lent pour plusieurs raisons. Tout d’abord, il faut que les décomptes de références hors cache soient modifiés régulièrement (voir Boost shared_ptr jusqu'à 10 × plus lent que la récupération de place OCaml ). Deuxièmement, les destructeurs injectés à la fin de l’étendue peuvent entraîner des appels de fonctions virtuelles inutiles et coûteux et inhiber les optimisations telles que l’élimination des appels de fin.

  3. Le comptage de références basé sur la portée permet de conserver les déchets flottants, car les objets ne sont pas recyclés jusqu'à la fin de la portée, alors que les GC de suivi peuvent les récupérer dès qu'ils deviennent inaccessibles, par ex. un local alloué avant une boucle peut-il être récupéré pendant la boucle?

  

Quels avantages la récupération de place peut-elle offrir à un développeur C ++ expérimenté?

La productivité et la fiabilité sont les principaux avantages. Pour de nombreuses applications, la gestion manuelle de la mémoire nécessite un effort de programmation important. En simulant une machine à mémoire infinie, la récupération de place libère le programmeur de ce fardeau qui lui permet de se concentrer sur la résolution de problèmes et évite certaines classes importantes de bogues (pointeurs flottants, free manquant, double free ). De plus, le ramassage des ordures facilite d’autres formes de programmation, par ex. en résolvant le problème d'amusement ascendant (1970) .

Dans un cadre prenant en charge GC, une référence à un objet immuable, tel qu'une chaîne, peut être transmise de la même manière qu'une primitive. Considérons la classe (C # ou Java):

public class MaximumItemFinder
{
  String maxItemName = "";
  int maxItemValue = -2147483647 - 1;

  public void AddAnother(int itemValue, String itemName)
  {
    if (itemValue >= maxItemValue)
    {
      maxItemValue = itemValue;
      maxItemName = itemName;
    }
  }
  public String getMaxItemName() { return maxItemName; }
  public int getMaxItemValue() { return maxItemValue; }
}

Notez que ce code n'a jamais rien à faire avec le contenu de l'une des chaînes, et peut simplement les traiter comme des primitives. Une instruction comme maxItemName = itemName; générera probablement deux instructions: un chargement de registre suivi d'un magasin de registre. MaximumItemFinder n'aura aucun moyen de savoir si les appelants de AddAnother conserveront une référence aux chaînes transmises, et les appelants n'auront aucun moyen de savoir combien de temps < code> MaximumItemFinder conservera les références. Les appelants de getMaxItemName n'auront aucun moyen de savoir si et quand MaximumItemFinder et le fournisseur d'origine de la chaîne renvoyée ont abandonné toutes les références à celle-ci. Comme le code peut simplement passer des références de chaîne comme des valeurs primitives, aucune de ces choses n’importe .

Notez également que même si la classe ci-dessus ne serait pas thread-safe en présence d'appels simultanés à AddAnother , tout appel à GetMaxItemName serait garanti renvoie une référence valide à une chaîne vide ou à l'une des chaînes qui ont été transmises à AddAnother . La synchronisation des threads serait nécessaire si l'on souhaitait établir une relation entre le nom d'élément maximal et sa valeur, mais la sécurité de la mémoire est assurée même en son absence .

Je ne pense pas qu'il soit possible d'écrire une méthode comme celle ci-dessus en C ++ qui maintiendrait la sécurité de la mémoire en présence d'une utilisation multithread arbitraire sans utiliser la synchronisation de threads ou nécessitant que chaque variable chaîne ait sa propre copie. de son contenu, stocké dans son propre espace de stockage, qui ne peut être libéré ni déplacé pendant la durée de vie de la variable en question. Il ne serait certainement pas possible de définir un type de référence de chaîne qui pourrait être défini, attribué et transmis aussi peu coûteux qu'un int .

La collecte des ordures peut causer des fuites, votre pire cauchemar

Un CPG à part entière qui gère des éléments tels que les références cycliques serait en quelque sorte une mise à niveau par rapport à un shared_ptr compté en références. Je l’accepterais un peu en C ++, mais pas au niveau de la langue.

L’un des avantages du C ++ est qu’il n’impose pas de récupération de place.

Je souhaite corriger une idée fausse commune: un mythe de collecte des déchets supprime en quelque sorte les fuites. D'après mon expérience, les pires cauchemars du code de débogage écrit par d'autres personnes et qui tentaient de repérer les fuites logiques les plus coûteuses impliquaient une récupération de place avec des langages tels que Python intégré via une application hôte gourmande en ressources.

Quand on parle de sujets comme le GC, il y a théorie et pratique. En théorie, c'est merveilleux et empêche les fuites. Pourtant, au niveau théorique, chaque langue est merveilleuse et sans fuites puisque, en théorie, tout le monde écrit un code parfaitement correct et teste tous les cas de figure pouvant être erronés.

La récupération de place associée à une collaboration d'équipe peu idéale a été à l'origine des pires fuites, les plus difficiles à résoudre, dans notre cas.

Le problème concerne toujours la propriété des ressources. Vous devez prendre des décisions de conception claires ici lorsque des objets persistants sont impliqués, et la récupération de place rend trop facile de penser que ce n’est pas le cas.

Avec certaines ressources, R , dans un environnement d’équipe où les développeurs ne communiquent pas et ne révisent pas constamment le code des autres utilisateurs (ce qui est un peu trop courant dans mon expérience), cela devient: Il est assez facile pour le développeur Un de stocker un descripteur sur cette ressource. Developer B fait de même, peut-être d'une manière obscure qui ajoute indirectement R à une structure de données. Il en va de même pour C . Dans un système récupéré, cela a créé 3 propriétaires de R .

Étant donné que le développeur A est celui qui a créé la ressource à l'origine et pense en être le propriétaire, il se souvient d'avoir libéré la référence à R lorsque l'utilisateur a indiqué qu'il ne veut plus l'utiliser. Après tout, s’il ne le fait pas, rien ne se passera et il sera évident, à l’aide de tests, que la logique de suppression par l’utilisateur final ne fait rien. Il se souvient donc de le publier, comme le ferait tout développeur raisonnablement compétent. Cela déclenche un événement pour lequel B le gère et se souvient également de libérer la référence à R .

Cependant, C oublie. Il n’est pas l’un des meilleurs développeurs de l’équipe: il s’agit d’une recrue un peu nouvelle qui travaille dans le système depuis un an seulement. Ou peut-être qu'il ne fait même pas partie de l'équipe, il est juste un développeur tiers populaire écrivant des plugins pour notre produit que de nombreux utilisateurs ajoutent au logiciel. Avec la récupération de place, nous obtenons ces fuites de ressources logiques silencieuses. Ils sont du pire type: ils ne se manifestent pas nécessairement dans la partie visible du logiciel du logiciel comme un bogue évident, outre le fait que, pendant les durées d’exécution du programme, l’utilisation de la mémoire ne cesse de croître et de croître dans un but mystérieux. Essayer de limiter ces problèmes avec un débogueur peut s’avérer aussi amusant que de déboguer une condition de concurrence urgente.

Sans la récupération de place, le développeur C aurait créé un pointeur suspendu . Il peut essayer d'y accéder à un moment donné et provoquer le blocage du logiciel. Voilà un bogue test / visible par l'utilisateur. C est un peu gêné et corrige son bug. Dans le scénario GC, essayer de savoir où le système fuit peut être si difficile que certaines fuites ne sont jamais corrigées. Ce ne sont pas des fuites physiques de type valgrind qui peuvent être facilement détectées et localisées avec une ligne de code spécifique.

Avec la récupération de place, le développeur C a créé une fuite très mystérieuse. Son code peut continuer à accéder à R qui n'est plus qu'une entité invisible dans le logiciel, non pertinente pour l'utilisateur à ce stade, mais toujours dans un état valide. Et comme le code de C crée davantage de fuites, il crée davantage de traitements cachés sur des ressources non pertinentes, et le logiciel non seulement perd de la mémoire, mais de plus en plus lent à chaque fois.

Ainsi, le nettoyage de la mémoire n'atténue pas nécessairement les fuites de ressources logiques. Cela peut, dans des scénarios moins qu'idéaux, rendre les fuites beaucoup plus faciles à passer inaperçues en silence et rester dans le logiciel. Les développeurs peuvent être tellement frustrés d’essayer de localiser leurs fuites logiques dans le GC qu’ils demandent simplement à leurs utilisateurs de redémarrer le logiciel périodiquement en guise de solution de contournement. Cela élimine les pointeurs en suspens, et dans un logiciel obsédé par la sécurité où le crash est totalement inacceptable dans n'importe quel scénario, je préférerais alors GC. Mais je travaille souvent dans des produits moins critiques pour la sécurité mais exigeant beaucoup de ressources et de performances, dans lesquels un crash qui peut être corrigé rapidement est préférable à un bug silencieux vraiment obscur et mystérieux, et où les fuites de ressources ne sont pas des bugs mineurs.

Dans les deux cas, nous parlons d'objets persistants ne résidant pas dans la pile, tels qu'un graphe de scène dans un logiciel 3D ou les clips vidéo disponibles dans un compositeur ou les ennemis dans un monde de jeu. Lorsque les ressources lient leur durée de vie à la pile, C ++ et tout autre langage GC ont tendance à rendre triviale la gestion correcte des ressources. La vraie difficulté réside dans les ressources persistantes référençant d'autres ressources.

En C ou C ++, vous pouvez créer des pointeurs et des blocages résultant de segfaults si vous ne parvenez pas à désigner clairement le propriétaire d'une ressource et le moment auquel les descripteurs doivent être libérés (par exemple, défini sur null en réponse à un événement). Pourtant, en GC, ce crash puissant et odieux mais souvent facile à localiser est échangé contre une fuite de ressources silencieuse qui pourrait ne jamais être détectée.

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