28 décembre 2024

Portage du code C# en C++ : Modèles de gestion de la mémoire

Notre framework, CodePorting.Translator Cs2Cpp, permet la publication de bibliothèques développées pour la plateforme .NET en C++. Dans cet article, nous discuterons de la façon dont nous avons réussi à concilier les modèles de gestion de la mémoire de ces deux langages et à garantir le bon fonctionnement du code traduit dans un environnement non géré.

Vous découvrirez les pointeurs intelligents que nous utilisons et pourquoi nous avons dû développer nos propres implémentations pour eux. De plus, nous couvrirons le processus de préparation du code C# pour la portabilité du point de vue de la gestion du cycle de vie des objets, certains des défis rencontrés, et les méthodes de diagnostic spécifiques que nous devons utiliser dans notre travail.

Modèle de Gestion de la Mémoire en C#

Le code C# est exécuté dans un environnement géré avec collecte de déchets. Pour les besoins de la traduction, cela signifie principalement qu'un programmeur C#, contrairement à un développeur C++, est libéré de la nécessité de rendre la mémoire allouée sur le tas qui n'est plus utilisée au système. Ceci est réalisé par le garbage collector (GC)—un composant de l'environnement CLR qui vérifie périodiquement quels objets sont encore utilisés dans le programme et nettoie ceux qui n'ont plus de références actives.

Une référence est considérée active si elle est :

  1. Située dans la pile : variables locales, arguments de méthodes ;
  2. Située dans la zone de données statiques : champs et propriétés statiques ;
  3. Située dans un objet qui a des références actives.

L'algorithme du garbage collector est généralement décrit comme traversant le graphe des objets, en commençant par les références situées dans la pile et dans les champs statiques, puis en supprimant tout ce qui n'a pas été atteint à l'étape précédente. Pour protéger contre l'invalidation du graphe de références pendant l'opération du GC, un mécanisme de stop-the-world est mis en œuvre. Cette description peut être simplifiée, mais elle est suffisante pour nos besoins.

Contrairement aux pointeurs intelligents, l'approche de la collecte des déchets est exempte du problème des références croisées ou cycliques. Si deux objets se réfèrent l'un à l'autre, éventuellement à travers un certain nombre d'objets intermédiaires, cela n'empêche pas le GC de les supprimer lorsque le groupe entier, connu sous le nom d'île d'isolation, n'a plus de références actives. Par conséquent, les programmeurs C# n'ont aucun préjugé contre la liaison des objets entre eux à tout moment et dans n'importe quelle combinaison.

Les types de données en C# sont divisés en types de référence et types de valeur. Les instances de types de valeur sont toujours situées dans la pile, dans la mémoire statique ou directement dans les champs de structures et d'objets. En revanche, les instances de types de référence sont toujours créées sur le tas, tandis que seules leurs références (adresses) sont stockées dans la pile, dans la mémoire statique et dans les champs. Les types de valeur comprennent les structures, les valeurs arithmétiques primitives et les références. Les types de référence comprennent les classes et, historiquement, les délégués. Les seules exceptions à cette règle sont les cas de boxing, où une structure ou un autre type de valeur est copié sur le tas pour être utilisé dans un contexte spécifique.

Le moment de la destruction de l'objet n'est pas défini : le langage garantit seulement qu'il ne se produira pas avant que la dernière référence active à l'objet ne soit supprimée. Si, dans le code converti, l'objet est détruit immédiatement après la suppression de la dernière référence active, cela ne perturbera pas le fonctionnement du programme.

Un autre point important est lié au support des types et méthodes génériques en C#. C# permet d'écrire des génériques une fois, puis de les utiliser avec des paramètres de type de référence et de type de valeur. Comme il sera montré plus tard, ce point s'avère significatif.

Modèle de Mappage des Types de C# vers C++

Quelques mots sur la façon dont nous mappons les types C# aux types C++. Puisque le framework CodePorting.Translator Cs2Cpp est destiné à porter des bibliothèques plutôt que des applications, une exigence importante pour nous est de reproduire l'API du projet .NET original aussi fidèlement que possible. Par conséquent, nous convertissons les classes, interfaces et structures C# en classes C++ héritant des types de base correspondants.

Par exemple, considérez le code suivant :

interface I1 {}
interface I2 {}
interface I3 : I2 {}
class A {}
class B : A, I1 {}
class C : B, I2 {}
class D : C, I3 {}
class Generic<T> { public T value; }
struct S {}

Il sera traduit comme suit :

class I1 : public virtual System::Object {};
class I2 : public virtual System::Object {};
class I3 : public virtual I2 {};
class A : public virtual System::Object {};
class B : public A, public virtual I1 {};
class C : public B, public virtual I2 {};
class D : public C, public virtual I3 {};
template <typename T> class Generic { public: T value; };
class S : public System::Object {};

La classe System::Object est une classe système déclarée dans la bibliothèque de support du traducteur, externe au projet converti. Les classes et interfaces héritent virtuellement d'elle pour éviter le problème du diamant. Les structures dans le code C++ traduit héritent de System::Object, tandis qu'en C#, elles en héritent via System.ValueType, mais cet héritage supplémentaire est supprimé pour des raisons d'optimisation. Les types génériques et les méthodes sont traduits en classes et méthodes template, respectivement.

Les instances de types traduits doivent respecter les mêmes garanties fournies par C#. Les instances de classes doivent être créées sur le tas, et leur durée de vie doit être déterminée par la durée de vie des références actives à elles. Les instances de structures doivent être créées dans la pile, sauf en cas de boxing. Les délégués sont un cas spécial et relativement trivial qui dépasse le cadre de cet article.

À Suivre

Nous avons considéré comment le modèle de gestion de la mémoire en C# affecte le processus de conversion du code en C++. Dans la prochaine partie de l'article, nous discuterons de la façon dont la décision a été prise sur la méthode de gestion de la durée de vie des objets alloués sur le tas et de la façon dont cette approche a été mise en œuvre.

Nouvelles connexes

Vidéos associées

Articles liés