web-dev-qa-db-fra.com

Comment implémenter le garbage collection en C ++

J'ai vu un article sur l'implémentation de GC en C et certaines personnes ont dit qu'il était impossible de le faire car C est faiblement typé. Je veux savoir comment implémenter GC en C++.

Je veux une idée générale sur la façon de le faire. Merci beaucoup!

Ceci est une question d'entrevue Bloomberg que mon ami m'a dit. Il a mal fait à ce moment-là. Nous voulons connaître vos idées à ce sujet.

47
Josh Morrison

La récupération de place en C et C++ sont deux sujets difficiles pour plusieurs raisons:

  1. Les pointeurs peuvent être transtypés en nombres entiers et vice-versa. Cela signifie que je pourrais avoir un bloc de mémoire accessible uniquement en prenant un entier, en le transtypant en un pointeur, puis en le déréférençant. Un garbage collector doit faire attention à ne pas penser qu'un bloc est inaccessible alors qu'en effet il peut toujours être atteint.

  2. Les pointeurs ne sont pas opaques. De nombreux récupérateurs de place, comme les collecteurs d'arrêt et de copie, aiment déplacer des blocs de mémoire ou les compacter pour économiser de l'espace. Puisque vous pouvez explicitement regarder les valeurs des pointeurs en C et C++, cela peut être difficile à implémenter correctement. Vous devez être sûr que si quelqu'un faisait quelque chose de délicat avec la transtypage en nombres entiers, vous mettiez correctement à jour l'entier si vous déplaciez un bloc de mémoire.

  3. La gestion de la mémoire peut être effectuée de manière explicite. Tout garbage collector devra tenir compte du fait que l'utilisateur est en mesure de libérer explicitement des blocs de mémoire à tout moment.

  4. En C++, il y a une séparation entre allocation/désallocation et construction/destruction d'objet. Un bloc de mémoire peut être alloué avec suffisamment d'espace pour contenir un objet sans qu'aucun objet n'y soit réellement construit. Un bon garbage collector devrait savoir, lorsqu'il récupère de la mémoire, s'il faut ou non appeler le destructeur pour tout objet qui pourrait y être alloué. Cela est particulièrement vrai pour les conteneurs de bibliothèque standard, qui utilisent souvent std::allocator pour utiliser cette astuce pour des raisons d'efficacité.

  5. La mémoire peut être allouée à partir de différentes zones. C et C++ peuvent obtenir de la mémoire soit à partir du magasin gratuit intégré (malloc/free ou new/delete), soit à partir du système d'exploitation via mmap ou d'autres appels système, et, dans le cas de C++, à partir de get_temporary_buffer ou return_temporary_buffer. Les programmes peuvent également obtenir de la mémoire d'une bibliothèque tierce. Un bon garbage collector doit être capable de suivre les références à la mémoire dans ces autres pools et (éventuellement) devrait être responsable de leur nettoyage.

  6. Les pointeurs peuvent pointer au milieu d'objets ou de tableaux. Dans de nombreux langages récupérés comme Java, les références aux objets pointent toujours vers le début de l'objet. En C et C++, les pointeurs peuvent pointer vers le milieu des tableaux, et en C++ vers le milieu des objets (si l'héritage multiple est utilisé). Cela peut considérablement compliquer la logique de détection de ce qui est encore accessible.

Donc, en bref, il est extrêmement difficile de créer un garbage collector pour C ou C++. La plupart des bibliothèques qui effectuent le ramasse-miettes en C et C++ sont extrêmement conservatrices dans leur approche et sont techniquement malsaines - elles supposent que vous ne prendrez pas, par exemple, un pointeur, le convertissez en un entier, l'écrivez sur le disque, puis chargez le remettre à un moment ultérieur. Ils supposent également que toute valeur en mémoire de la taille d'un pointeur pourrait éventuellement être un pointeur, et refusent donc parfois de libérer de la mémoire inaccessible car il y a une chance non nulle qu'il y ait un pointeur.

Comme d'autres l'ont souligné, le Boehm GC fait la collecte des ordures pour C et C++, mais sous réserve des restrictions susmentionnées.

Fait intéressant, C++ 11 inclut de nouvelles fonctions de bibliothèque qui permettent au programmeur de marquer les régions de la mémoire comme accessibles et inaccessibles en prévision des futurs efforts de collecte des ordures. Il sera peut-être possible à l'avenir de créer un très bon garbage collector C++ 11 avec ce type d'informations. En attendant cependant, vous devrez être extrêmement prudent pour ne violer aucune des règles ci-dessus.

55
templatetypedef

C n'est pas C++, mais les deux ont les mêmes problèmes "faiblement typés". Ce ne sont pas les transcriptions implicites qui causent un problème, mais la tendance à "punner" (renverser le système de types), en particulier dans les bibliothèques de structures de données.

Il y a ramasse-miettes pour C et/ou C++. Le collectionneur conservateur de Boehm est probablement le plus connu. C'est conservateur en ce sens que s'il voit un motif de bits qui ressemble à un pointeur sur un objet, il ne collecte pas cet objet. Cette valeur peut être un autre type de valeur complètement, donc l'objet peut être collecté, mais "conservateur" signifie jouer en toute sécurité.

Même un collecteur conservateur peut être dupe, cependant, si vous utilisez des pointeurs calculés. Il y a une structure de données, par exemple, où chaque nœud de liste a un champ donnant la différence entre les adresses du nœud suivant et du nœud précédent. L'idée est de donner un comportement de liste à double lien avec un seul lien par nœud, au détriment des itérateurs plus complexes. Puisqu'il n'y a aucun pointeur explicite n'importe où vers la plupart des nœuds, ils peuvent être mal collectés.

Il s'agit bien sûr d'un cas particulier très exceptionnel.

Plus important encore - vous pouvez avoir des destructeurs fiables ou un ramasse-miettes, pas les deux. Lorsqu'un cycle d'ordures est collecté, le collecteur ne peut pas décider quel destructeur appeler en premier.

Étant donné que le modèle RAII est omniprésent en C++, et qui repose sur des destructeurs, il existe un conflit OMI. Il peut y avoir des exceptions valides, mais mon avis est que si vous voulez une récupération de place, vous devez utiliser un langage conçu à partir de zéro pour la récupération de place (Java, C #, ...).

4
Steve314

Regardez dans le Boehm Garbage Collector .

3
yan

Vous pouvez soit utiliser des pointeurs intelligents, soit créer votre propre objet conteneur qui suivra les références et gérera l'allocation de mémoire, etc. Des pointeurs intelligents seraient probablement préférables. Souvent, vous pouvez éviter complètement l'allocation dynamique de tas.

Par exemple:

char* pCharArray = new char[128];
// do some stuff with characters
delete [] pCharArray;

Le danger avec ce qui précède étant que quelque chose se lance entre le nouveau et la suppression, votre suppression ne sera pas exécutée. Quelque chose comme ci-dessus pourrait facilement être remplacé par un code plus sûr "ordures récupérées":

std::vector<char> charArray;
// do some stuff with characters

Bloomberg a des questions d'entrevue notoirement non pertinentes d'un point de vue pratique de codage. Comme la plupart des enquêteurs, ils se préoccupent principalement de votre façon de penser et de vos compétences en communication que de la solution réelle.

3
AJG85

L'affirmation que vous avez vue est fausse; le collecteur Boehm prend en charge C et C++. Je suggère de lire la documentation du collecteur Boehm (en particulier cette page ) pour un bon aperçu de la façon dont on pourrait écrire un ramasse-miettes en C ou C++.

0
Marcelo Cantos

Vous pouvez lire sur la structure shared_ptr .

Il implémente un simple comptage de références garbage collector.

Si vous voulez un véritable garbage collector, vous pouvez surcharger le nouvel opérateur .

Créez une structure similaire à shared_ptr, appelez-la Object.

Cela enveloppera le nouvel objet créé. Maintenant, en surchargeant ses opérateurs, vous pouvez contrôler le GC.

Il ne vous reste plus qu'à implémenter l'un des nombreux algorithmes GC

0
Yochai Timmer