Dilemme lié à l’utilisation de types de valeur avec l’opérateur `new` en C #

Lorsque operator new() est utilisé avec le type de référence, l’espace pour l’instance est alloué sur le tas et la variable de référence elle-même est placée sur la stack. De plus, tout ce qui se trouve dans l’instance de type référence, qui est allouée sur le tas, est mis à zéro.
Par exemple, voici une classe:

 class Person { public int id; public ssortingng name; } 

Dans le code suivant:

 class PersonDemo { static void Main() { Person p = new Person(); Console.WriteLine("id: {0} name: {1}", p.id, p.name); } } 

p variable p est sur la stack et l’instance créée de Person (tous ses membres) est sur le tas. p.id serait 0 et p.name serait null . Ce serait le cas car tout ce qui est alloué sur le tas est mis à zéro.

Maintenant, ce qui me rend confus, c’est si j’utilise un type de valeur avec un new opérateur. Par exemple, prenez en compte la structure suivante:

 struct Date { public int year; public int month; public int day; } class DateDemo { static void Main() { Date someDate; someDate= new Date(); Console.WriteLine("someDate is: {0}/{1}/{2}", someDate.month, someDate.day, someDate.year); } } 

Maintenant, j’aimerais savoir ce que font les lignes suivantes de main:

  Date someDate; someDate= new Date(); 

En première ligne, someDate variable someDate est allouée sur la stack. Précisément 12 octets.
Ma question est ce qui se passe sur la deuxième ligne? Que fait l’ operator new() ? Est-ce que cela ne fait que supprimer les membres de la structure Date ou qu’il alloue également de l’espace sur le tas? D’un côté, je ne m’attendrais pas à ce que new alloue de l’espace sur le tas, bien sûr car sur la première ligne, la mémoire est déjà allouée sur la stack pour l’instance de structure. D’un autre côté, je m’attendrais à ce que new alloue de l’espace sur le tas et renvoie l’adresse de cet espace, car c’est ce que new devrait faire. C’est peut-être parce que je viens de fond C ++.

Néanmoins, si la réponse est: “quand new est utilisé avec des types valeur, il ne fait que zéros-out des membres d’object”, alors qu’il s’agit d’un sens un peu incohérent de new car parce que:

  1. lors de l’utilisation de new avec des types de valeur, seuls les membres de l’object sur la stack sont supprimés
  2. lorsqu’il utilise new avec des types de référence, il alloue de la mémoire sur le tas pour l’instance et supprime ses membres

Merci d’avance,
À votre santé

Laissez-moi d’abord corriger vos erreurs.

Lorsque operator new () est utilisé avec le type de référence, l’espace pour l’instance est alloué sur le tas et la variable de référence elle-même est placée sur la stack.

La référence résultant de “new” est une valeur , pas une variable . La valeur fait référence à un emplacement de stockage.

La référence est bien sûr renvoyée dans un registre de la CPU . Que le contenu de ce registre CPU soit ou non copié dans la stack d’appels revient à l’optimiseur de la gigue. Il ne doit pas toujours vivre sur la stack; il peut vivre indéfiniment dans des registres ou être copié directement du registre dans le segment de mémoire géré ou, dans le code peu sûr, dans une mémoire non gérée.

La stack est un détail d’implémentation. Vous ne savez pas quand la stack est utilisée à moins que vous ne regardiez le code jitté.

La variable p est sur la stack et l’instance créée de Person (tous ses membres) est sur le tas. p.id serait 0 et p.name serait null.

Correct, bien que bien sûr, p puisse être réalisé comme un registre si la gigue le décide. Il n’est pas nécessaire d’utiliser la stack s’il existe des registres.

Vous semblez assez accroché à cette idée que la stack est utilisée. La gigue peut avoir un grand nombre de registres à sa disposition, et ces registres peuvent être très gros.

Je viens de fond C ++.

Ah, ça explique pourquoi vous êtes tellement accroché sur cette stack contre le tas. Apprenez à cesser de vous inquiéter à ce sujet. Nous avons conçu un environnement de mémoire gérée dans lequel les choses vivent aussi longtemps qu’elles sont nécessaires. Que le responsable choisisse d’utiliser une stack, un tas ou des registres pour gérer efficacement la mémoire, cela dépend de lui.

En première ligne, une variable someDate est allouée sur la stack. Précisément 12 octets.

Supposons, pour les besoins de l’argumentation, que cette structure à 12 octets est allouée sur la stack. Semble raisonnable.

Ma question est ce qui se passe sur la deuxième ligne? Que fait l’opérateur new ()? Est-ce que cela ne fait que supprimer les membres de la structure Date ou qu’il alloue également de l’espace sur le tas?

La question présuppose une fausse dichotomie et il est donc impossible de répondre comme indiqué. La question présente deux alternatives possibles, dont aucune n’est nécessairement correcte.

D’un côté, je ne m’attendrais pas à ce que new alloue de l’espace sur le tas, bien sûr car sur la première ligne, la mémoire est déjà allouée sur la stack pour l’instance de structure.

Conclusion correcte, raisonnement spécieux. Aucune allocation de segment de mémoire n’est effectuée car le compilateur sait qu’aucune partie de cette opération ne nécessite un stockage de longue durée . C’est à ça que sert le tas; lorsque le compilateur détermine qu’une variable donnée peut vivre plus longtemps que l’activation de la méthode en cours, il génère un code qui alloue la mémoire de stockage pour cette variable sur la mémoire de type “tas” à vie longue. S’il détermine que la variable a définitivement une durée de vie courte, il utilise la stack (ou les registres), comme optimisation.

D’un autre côté, je m’attendrais à ce que new alloue de l’espace sur le tas et renvoie l’adresse de cet espace, car c’est ce que new devrait faire.

Incorrect. “new” ne garantit pas que le tas est alloué. Au lieu de cela, “nouveau” garantit qu’un constructeur est appelé sur une mémoire mise à zéro.

Revenons à votre question:

Est-ce que cela ne fait que supprimer les membres de la structure Date ou qu’il alloue également de l’espace sur le tas?

Nous soaps qu’il n’alloue pas d’espace sur le tas. Est-ce qu’il met à zéro les membres de la structure de date?

C’est une question compliquée. La spécification dit que ce qui se passe quand vous dites

 someDate = new Date(); 
  • l’adresse de someDate est déterminée
  • l’espace est alloué (hors “la stack”) pour le nouvel object. Il est mis à zéro.
  • alors le constructeur, s’il y en a un, est appelé, “this” étant une référence au nouveau stockage en stack
  • les octets du nouveau stockage de stack sont alors copiés à l’adresse someDate.

Maintenant, est-ce vraiment ce qui se passe ? Vous auriez parfaitement le droit de remarquer qu’il est impossible de dire si un nouvel espace de stack est alloué, initialisé et copié, ou si le “vieil” espace de stack est initialisé.

La réponse est que, dans les cas où le compilateur déduit qu’il est impossible pour l’utilisateur de remarquer que l’espace de stack existant est en cours de mutation, l’espace de stack existant est muté et que l’allocation supplémentaire et la copie ultérieure sont supprimées .

Dans les cas où le compilateur est incapable d’en déduire, un emplacement de stack temporaire est créé, initialisé à zéro, construit, muté par le constructeur, puis la valeur résultante est copiée dans la variable. Cela garantit que si le constructeur lève une exception, vous ne pouvez pas observer un état incohérent dans la variable.

Pour plus de détails sur ce problème et son parsing par le compilateur, voir mon article sur le sujet.

http://blogs.msdn.com/b/ericlippert/archive/2010/10/11/debunking-another-myth-about-value-types.aspx

OK voici un simple:

 class Program { static void Main(ssortingng[] args) { DateTime dateTime = new DateTime(); dateTime = new DateTime(); Console.Read(); } } 

qui comstack à ce code IL:

 .method private hidebysig static void Main(ssortingng[] args) cil managed { .entrypoint // Code size 24 (0x18) .maxstack 1 .locals init ([0] valuetype [mscorlib]System.DateTime dateTime) IL_0000: nop IL_0001: ldloca.s dateTime IL_0003: initobj [mscorlib]System.DateTime IL_0009: ldloca.s dateTime IL_000b: initobj [mscorlib]System.DateTime IL_0011: call int32 [mscorlib]System.Console::Read() IL_0016: pop IL_0017: ret } // end of method Program::Main 

Comme vous pouvez le constater, CLR utilisera la même variable locale pour stocker le nouveau type de valeur, mais exécutera à nouveau le constructeur – ce qui ne fera probablement que mettre à zéro la mémoire . Nous ne pouvons pas voir ce initobj est, c’est une implémentation CLR .

Comme l’explique Eric Lippert, la réalité est qu’il n’existe pas de règle générale sur les types de valeur alloués sur la stack . Ceci est purement imputable à la mise en œuvre du CLR.

Le constructeur par défaut d’une structure retourne une structure avec toute la mémoire mise à zéro. C’est-à-dire que new SomeStruct() est identique à default(SomeStruct) .

Votre code affecte ensuite cette structure par défaut à votre variable.

C’est tout ce que vous savez à coup sûr.

La manière dont le compilateur y parvient est entièrement l’affaire des compilateurs.

Mais si vous êtes curieux de savoir en coulisses, le compilateur va probablement simplement effacer directement l’emplacement de la stack de cette variable: en supposant que cette variable est stockée dans la stack. Beaucoup de choses peuvent empêcher cela – un exemple est une fonction anonyme qui y accède, à savoir:

 Func PersonFactory() { Person p = new Person(); return () => p; } 

Ici, p doit être stocké sur le tas pour pouvoir exister une fois que la fonction est renvoyée, etc., de sorte que new Person() effacera l’emplacement de ce tas.

En tous cas. Contrairement à C / C ++, avec C #, il est judicieux d’oublier “la stack”, “le tas”, etc. D’après ce que je sais, la spécification du langage n’a pas de concept de l’un ni de l’autre. Elles sont toutes spécifiques à l’implémentation. Qui sait, certaines mises en œuvre futures peuvent, lorsque l’parsing d’évasion le permet, mettre des valeurs de tas sur la stack pour économiser un peu d’effort pour le GC. Il est vraiment préférable de ne pas prendre de décisions de conception spécifiques à une implémentation donnée de la spécification C #.

Du sharepoint vue du développeur, vous ne savez pas où il est atsortingbué. Par exemple, un périphérique exotique avec CLR qui n’a aucune idée de la stack -> tout va dans le tas. Même si vous considérez le bureau CLR, JITer peut dans certains cas déplacer des variables de la stack au tas.

Plus d’informations.

A propos de la mise à zéro des structs.

Les parameters du constructeur sans paramètre zéros.

Si vous n’utilisez pas new() , vous ne pourrez pas accéder aux membres de la structure à moins de les initialiser vous-même. Sinon, vous obtiendrez “Utilisation d’un champ éventuellement non atsortingbué”.