Lorsque vous créez une instance d’une classe avec le opérateur, la mémoire est alloué sur le tas. Lorsque vous créez une instance d’un struct avec le
opérateur où la mémoire obtenir allouée, sur le tas ou sur la pile ?
Réponses
Trop de publicités?Bon, voyons voir si je peux faire plus clair.
Tout d'abord, les Cendres du droit: la question n'est pas sur l'endroit où la valeur de type de variables sont alloués. C'est une autre question - à laquelle la réponse n'est pas juste "sur la pile". C'est plus compliqué que ça (et rendue encore plus compliquée par C# 2). J'ai un article sur le sujet et de développer, si demandé, mais nous allons faire face à tout l' new
de l'opérateur.
Deuxièmement, tout cela dépend vraiment de ce niveau que vous êtes en train de parler. Je suis en train de regarder ce que le compilateur ne avec le code source, dans les termes de l'IL qu'il crée. C'est plus que possible que le compilateur JIT va faire des choses intelligentes en termes d'optimisation de loin beaucoup de "logique" de l'allocation.
Troisièmement, je suis ignorant les génériques, surtout parce que je ne connais pas la réponse, et en partie parce qu'il ne ferait que compliquer les choses trop.
Enfin, tout ceci n'est qu'avec l'implémentation actuelle. Le C# spec ne spécifie pas beaucoup de cela - c'est effectivement un détail d'implémentation. Il y a ceux qui croient que le code managé les développeurs ne devrais vraiment pas de soins. Je ne suis pas sûr que je serais aller aussi loin, mais il vaut la peine d'imaginer un monde où, en fait, toutes les variables locales en direct sur le tas qui serait toujours conforme avec la spécification.
Il y a deux situations différentes avec l' new
de l'opérateur sur les types de valeur: vous pouvez soit appeler un constructeur sans paramètre (par exemple, new Guid()
) ou un parameterful constructeur (par exemple, new Guid(someString)
). Ces générer significativement différente de l'IL. Pour comprendre pourquoi, vous devez comparer le C# et CLI spécifications: selon C#, tous les types de valeur avoir un constructeur sans paramètre. Selon les spécifications CLI, aucun des types de valeur ont sans paramètre constructeurs. (Chercher les constructeurs d'un type de valeur, de réflexion, de temps - vous ne trouverez pas un sans paramètre.)
Il fait sens pour C#, pour traiter de l'initialiser une valeur avec des zéros" en tant que constructeur, car il maintient la langue constant, vous pouvez penser à de la new(...)
comme toujours appeler un constructeur. Il fait sens pour la CLI de le penser autrement, comme il n'existe pas de code pour appeler - et certainement pas spécifiques à un type de code.
Il fait aussi une grande différence de ce que vous allez faire avec la valeur une fois que vous avez initialisé. La IL a utilisé pour
Guid localVariable = new Guid(someString);
est différente de la IL a utilisé pour:
myInstanceOrStaticVariable = new Guid(someString);
En outre, si la valeur est utilisée comme une valeur intermédiaire, par exemple, un argument à un appel de méthode, les choses sont légèrement différentes de nouveau. Pour montrer toutes ces différences, voici un petit programme de test. Il ne montre pas la différence entre les variables et les variables d'instance: IL ne diffèrent entre stfld
et stsfld
, mais c'est tout.
using System;
public class Test
{
static Guid field;
static void Main() {}
static void MethodTakingGuid(Guid guid) {}
static void ParameterisedCtorAssignToField()
{
field = new Guid("");
}
static void ParameterisedCtorAssignToLocal()
{
Guid local = new Guid("");
// Force the value to be used
local.ToString();
}
static void ParameterisedCtorCallMethod()
{
MethodTakingGuid(new Guid(""));
}
static void ParameterlessCtorAssignToField()
{
field = new Guid();
}
static void ParameterlessCtorAssignToLocal()
{
Guid local = new Guid();
// Force the value to be used
local.ToString();
}
static void ParameterlessCtorCallMethod()
{
MethodTakingGuid(new Guid());
}
}
Voici le IL pour la classe, à l'exclusion hors de propos bits (tels que des opr):
.class public auto ansi beforefieldinit Test extends [mscorlib]System.Object
{
// Removed Test's constructor, Main, and MethodTakingGuid.
.method private hidebysig static void ParameterisedCtorAssignToField() cil managed
{
.maxstack 8
L_0001: ldstr ""
L_0006: newobj instance void [mscorlib]System.Guid::.ctor(string)
L_000b: stsfld valuetype [mscorlib]System.Guid Test::field
L_0010: ret
}
.method private hidebysig static void ParameterisedCtorAssignToLocal() cil managed
{
.maxstack 2
.locals init ([0] valuetype [mscorlib]System.Guid guid)
L_0001: ldloca.s guid
L_0003: ldstr ""
L_0008: call instance void [mscorlib]System.Guid::.ctor(string)
// Removed ToString() call
L_001c: ret
}
.method private hidebysig static void ParameterisedCtorCallMethod() cil managed
{
.maxstack 8
L_0001: ldstr ""
L_0006: newobj instance void [mscorlib]System.Guid::.ctor(string)
L_000b: call void Test::MethodTakingGuid(valuetype [mscorlib]System.Guid)
L_0011: ret
}
.method private hidebysig static void ParameterlessCtorAssignToField() cil managed
{
.maxstack 8
L_0001: ldsflda valuetype [mscorlib]System.Guid Test::field
L_0006: initobj [mscorlib]System.Guid
L_000c: ret
}
.method private hidebysig static void ParameterlessCtorAssignToLocal() cil managed
{
.maxstack 1
.locals init ([0] valuetype [mscorlib]System.Guid guid)
L_0001: ldloca.s guid
L_0003: initobj [mscorlib]System.Guid
// Removed ToString() call
L_0017: ret
}
.method private hidebysig static void ParameterlessCtorCallMethod() cil managed
{
.maxstack 1
.locals init ([0] valuetype [mscorlib]System.Guid guid)
L_0001: ldloca.s guid
L_0003: initobj [mscorlib]System.Guid
L_0009: ldloc.0
L_000a: call void Test::MethodTakingGuid(valuetype [mscorlib]System.Guid)
L_0010: ret
}
.field private static valuetype [mscorlib]System.Guid field
}
Comme vous pouvez le voir, il ya beaucoup de différentes instructions pour appeler le constructeur:
-
newobj
: Alloue de la valeur sur la pile, appelle un constructeur paramétré. Utilisé pour les valeurs intermédiaires, par exemple pour l'affectation à un champ ou l'utiliser comme un argument de méthode. -
call instance
: Utilise déjà allouée emplacement de stockage (que ce soit sur la pile ou pas). Il est utilisé dans le code ci-dessus pour l'affectation à une variable locale. Si la même variable locale est une valeur attribuée à plusieurs reprises à l'aide de plusieursnew
des appels, c'est juste initialise les données sur le haut de la vieille valeur - il ne pas allouer plus d'espace de pile à chaque fois. -
initobj
: Utilise déjà allouée emplacement de stockage et juste lingettes les données. Ce est utilisé pour tous nos constructeur sans paramètre appels, y compris ceux qui attribuent à une variable locale. Pour l'appel de méthode, un intermédiaire de la variable locale est effectivement introduit, et sa valeur effacé parinitobj
.
J'espère que cela montre la complexité du sujet, tout en mettant un peu de lumière sur elle en même temps. Dans certains conceptuel, chaque appel à l' new
alloue de l'espace sur la pile, mais comme nous l'avons vu, ce n'est pas ce qui se passe vraiment, même au niveau IL. J'aimerais attirer votre attention sur un cas particulier. Prendre cette méthode:
void HowManyStackAllocations()
{
Guid guid = new Guid();
// [...] Use guid
guid = new Guid(someBytes);
// [...] Use guid
guid = new Guid(someString);
// [...] Use guid
}
Que "logiquement" a 4 pile allocations - un pour la variable, et un pour chacune des trois new
des appels - mais en fait, la pile n'est attribuée une fois, et puis le même emplacement de stockage est réutilisé.
EDIT: Juste pour être clair, ce n'est vrai que dans certains cas... en particulier, la valeur de guid
ne sera pas visible si l' Guid
constructeur lève une exception, c'est pourquoi le compilateur C# est en mesure de réutiliser la même pile de logement. Voir Eric Lippert du blog sur la valeur de type de construction pour plus de détails et un cas où il n'a pas l' appliquer.
J'ai beaucoup appris en écrivant cette réponse - s'il vous plaît demander des précisions si tout cela n'est pas claire!
Le mémoire contenant une structure de champs peuvent être alloués sur la pile ou le tas, selon les circonstances. Si la structure-type de la variable est une variable locale ou un paramètre qui n'est pas capturé par l'anonymat d'un délégué ou un itérateur de classe, alors il sera alloué sur la pile. Si la variable est une partie de la classe, alors il sera alloué au sein de la classe sur le tas.
Si la structure est alloué sur le tas, puis l'appel de l'opérateur new n'est pas nécessaire d'allouer de la mémoire. Le seul but serait de définir les valeurs des champs en fonction de ce qui est dans le constructeur. Si le constructeur n'est pas appelé, alors tous les champs d'obtenir leurs valeurs par défaut (0 ou null).
De même pour les structures alloué sur la pile, sauf que C# nécessite toutes les variables locales à une valeur avant de les utiliser, de sorte que vous devez appeler un constructeur personnalisé ou le constructeur par défaut (un constructeur qui ne prend pas de paramètres est toujours disponible pour les structures).
Pour le mettre de façon compacte, la nouvelle est un abus de langage pour les structures, appelant de nouveaux appelle tout simplement le constructeur. Le seul emplacement de stockage de la structure est le lieu qu'il est défini.
Si c'est un membre de la variable est stockée directement dans quoi que ce soit définie, si c'est une variable locale ou un paramètre, il est stocké sur la pile.
Le contraste de ce cours, qui ont une référence où la structure aurait été conservée dans son intégralité, tandis que les points de référence quelque part sur le tas. (Membre, local/paramètre sur la pile)
Il peut aider à regarder un peu en C++, où il n'y a pas de véritable distinction entre la class/struct. (Il y a des noms similaires dans la langue, mais ils ne se réfèrent à la valeur par défaut de l'accessibilité des choses) Lorsque vous appelez nouveau, vous obtenez un pointeur sur le tas emplacement, tandis que si vous avez un non-référence de pointeur, il est stocké directement sur la pile ou dans l'autre objet, ala struct en C#.
Comme avec tous les types de valeur, les structures toujours aller là où ils ont été déclarés.
Voir cette question ici pour plus de détails sur l'utilisation des structures. Et cette question ici pour plus d'info sur les structures.
Edit: j'avais mistankely répondu qu'ils TOUJOURS aller dans la pile. C'est incorrect.
Je suis probablement manque quelque chose ici, mais pourquoi nous soucions-nous de l'allocation?
Types de valeur sont passés par valeur ;) et ne peut donc pas être muté à un champ d'application différent de celui où ils sont définis. Pour être en mesure de muter de la valeur, vous devez ajouter l' [ref] mot-clé.
Les types de référence sont passés par référence et peut être muté.
Il y a bien sûr immuable de référence types de chaînes étant la plus populaire.
Tableau de mise en page/l'initialisation: Types de valeur -> zéro de la mémoire [nom,code postal][nom,zip] Les types de référence -> zéro de la mémoire -> null [ref][ref]