new
İşleç ile bir sınıf örneği oluşturduğunuzda , bellek yığın üzerinde ayrılır. new
Operatöre bellek yığınının, yığının veya yığının nereden ayrıldığı bir yapı örneği oluşturduğunuzda ?
new
İşleç ile bir sınıf örneği oluşturduğunuzda , bellek yığın üzerinde ayrılır. new
Operatöre bellek yığınının, yığının veya yığının nereden ayrıldığı bir yapı örneği oluşturduğunuzda ?
Yanıtlar:
Tamam, bakalım bunu daha açık hale getirebilir miyim.
İlk olarak, Ash haklıdır: soru, değer tipi değişkenlerinin nereye tahsis edildiği ile ilgili değildir . Bu farklı bir soru - ve cevabın sadece “yığın üzerinde” olmadığı bir soru. Bundan daha karmaşık (ve C # 2 ile daha da karmaşık hale geldi). Bir var konuda makale ve istenirse üzerine genişletmek, ama sadece beraber anlaşma sağlayacak operatörü.new
İkincisi, tüm bunlar gerçekten hangi seviyeden bahsettiğinize bağlıdır. Derleyicinin kaynak koduyla ne yaptığını, oluşturduğu IL açısından bakıyorum. JIT derleyicisinin oldukça fazla "mantıksal" tahsisi optimize etmek açısından akıllıca şeyler yapması mümkün.
Üçüncüsü, jenerikleri görmezden geliyorum, çoğunlukla cevabı gerçekten bilmediğim için ve kısmen de işleri çok fazla karmaşıklaştıracağı için.
Son olarak, tüm bunlar sadece mevcut uygulama ile. C # spec bunun çoğunu belirtmez - etkili bir şekilde bir uygulama detayıdır. Yönetilen kod geliştiricilerinin gerçekten umursamaması gerektiğine inananlar var. O kadar ileri gideceğime emin değilim, ama aslında tüm yerel değişkenlerin öbek üzerinde yaşadığı bir dünyayı hayal etmeye değer - ki bu hala spesifikasyona uygun.
new
İşleç ile değer türlerinde iki farklı durum vardır: parametresiz bir kurucu (örn. new Guid()
) Veya parameterful bir kurucu (örn. new Guid(someString)
) Çağırabilirsiniz . Bunlar önemli ölçüde farklı IL üretir. Nedenini anlamak için, C # ve CLI özelliklerini karşılaştırmanız gerekir: C # 'a göre, tüm değer türlerinin parametresiz bir yapıcısı vardır. CLI spesifikasyonuna göre, hiçbir değer türünün parametresiz yapıcıları yoktur. (Değer türünün yapıcılarını bir süre yansımayla getirin - parametresiz bir tane bulamazsınız.)
O dil tutarlı tutar, çünkü C # yapıcı olarak "sıfır ile bir değer başlatmak" tedavi etmek için mantıklı - aklınıza gelebilecek new(...)
olarak her zaman bir kurucu çağırmak. Aramak için gerçek bir kod olmadığı ve kesinlikle türe özgü bir kod olmadığı için CLI'nin farklı düşünmesi mantıklıdır.
Ayrıca, başlattıktan sonra değerle ne yapacağınızı da fark eder. Kullanılan IL
Guid localVariable = new Guid(someString);
aşağıdakiler için kullanılan IL'den farklıdır:
myInstanceOrStaticVariable = new Guid(someString);
Buna ek olarak, değer bir ara değer olarak kullanılırsa, örneğin bir yöntem çağrısının bağımsız değişkeni, işler yeniden biraz farklıdır. Tüm bu farklılıkları göstermek için, kısa bir test programı. Statik değişkenler ve örnek değişkenler arasındaki farkı göstermez: IL stfld
ve arasında farklılık gösterir stsfld
, ama hepsi bu.
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());
}
}
Alakasız bitler (nops gibi) hariç olmak üzere sınıf için IL:
.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
}
Gördüğünüz gibi, yapıcıyı çağırmak için kullanılan birçok farklı talimat vardır:
newobj
: Yığındaki değeri atar, parametreli bir kurucu çağırır. Ara değerler için kullanılır, örneğin bir alana atama veya yöntem bağımsız değişkeni olarak kullanma.call instance
: Önceden ayrılmış bir depolama yeri kullanır (istifte olsun olmasın). Bu, yukarıdaki kodda yerel bir değişkene atamak için kullanılır. Aynı yerel değişken birkaç kullanarak bir değerini birkaç kez atanmışsa new
aramaları, sadece eski değerin üstüne üzerinden veri başlatır - bu değil fazla yığın alanı her zaman ayırmak.initobj
: Önceden ayrılmış bir depolama yeri kullanır ve verileri siler. Bu, yerel bir değişkene atananlar dahil, parametresiz tüm yapıcı çağrılarımız için kullanılır. Yöntem çağrısı için, bir ara yerel değişken etkili bir şekilde tanıtılır ve değeri tarafından silinir initobj
.Umarım bu, konunun ne kadar karmaşık olduğunu gösterir, aynı zamanda biraz ışık tutar. Gelen bazı kavramsal duyular, her çağrı new
yığını üzerinde ayırdığı alanı - ama biz gördüğümüz gibi, gerçekten hatta IL düzeyinde olur şey değildir. Belirli bir vakayı vurgulamak istiyorum. Bu yöntemi kullanın:
void HowManyStackAllocations()
{
Guid guid = new Guid();
// [...] Use guid
guid = new Guid(someBytes);
// [...] Use guid
guid = new Guid(someString);
// [...] Use guid
}
Bu "mantıksal olarak" 4 yığın tahsisine sahiptir - biri değişken için, diğeri üç new
çağrının her biri için - ancak aslında (bu belirli kod için) yığın yalnızca bir kez tahsis edilir ve aynı depolama alanı yeniden kullanılır.
DÜZENLEME: Sadece açık guid
olmak gerekirse , bu sadece bazı durumlarda doğrudur ... özellikle, Guid
yapıcı bir istisna atarsa , görünür olmaz , bu nedenle C # derleyicisi aynı yığın yuvasını yeniden kullanabilir. Daha fazla ayrıntı ve geçerli olmadığı bir vaka için Eric Lippert'in değer türü yapımıyla ilgili blog yayınına bakın .
Bu cevabı yazarken çok şey öğrendim - lütfen herhangi bir sorunun net olup olmadığını belirtin!
List<Guid>
ve ona eklediğinizde ne olur ? Bu 3 tahsis (aynı IL) olurdu? Ama büyülü bir yerde tutulurlar
guid
sadece üzerine yazılıp yazılmadığı önemli değildir.
Bir yapının alanlarını içeren bellek, koşullara bağlı olarak yığın veya yığın üzerinde tahsis edilebilir. Struct-type değişkeni, bazı anonim delege veya yineleyici sınıfı tarafından yakalanmayan yerel bir değişken veya parametre ise, yığına atanır. Değişken bir sınıfın parçasıysa, yığın içindeki sınıf içinde tahsis edilecektir.
Yapı yığın üzerinde tahsis edilirse, bellek ayırmak için yeni işleci çağırmak aslında gerekli değildir. Tek amaç, alan değerlerini yapıcıda ne olduğuna göre ayarlamaktır. Yapıcı çağrılmazsa, tüm alanlar varsayılan değerlerini alır (0 veya null).
Yığın üzerinde tahsis edilen yapılar için benzer şekilde, C # öğesinin kullanılmadan önce tüm yerel değişkenlerin bir değere ayarlanmasını gerektirmesi dışında, özel bir kurucu veya varsayılan kurucu (parametre almayan bir kurucu her zaman kullanılabilir yapılar).
Kompakt olarak ifade etmek gerekirse, yeni yapılar için bir yanlış adlandırmadır, yeni çağırmak sadece yapıcıyı çağırır. Yapı için tek depolama konumu tanımlandığı konumdur.
Bir üye değişkeni ise, içinde tanımlandığı her şeyde doğrudan saklanır, yerel bir değişken veya parametre ise yığına kaydedilir.
Bunu, yapının bütünüyle saklandığı her yerde bir referansı olan sınıflarla karşılaştırın; referans ise yığın üzerinde bir yere işaret eder. (Üye içindeki yerel / yığındaki parametre)
Sınıf / yapı arasında gerçek bir ayrımın olmadığı C ++ 'a biraz bakmanıza yardımcı olabilir. (Dilde benzer adlar vardır, ancak yalnızca şeylerin varsayılan erişilebilirliğine atıfta bulunurlar) Yeni aradığınızda, yığın konumuna bir işaretçi alırsınız, işaretçi olmayan bir referansınız varsa doğrudan yığına veya diğer nesne içinde ala C # 'da yapılandırır.
Tüm değer türlerinde olduğu gibi, yapılar her zaman bildirildikleri yere gider .
Bu soruya bakın burada yapılar ne zaman kullanılacağı hakkında daha fazla ayrıntı için. Ve bu soru burada yapılar hakkında daha fazla bilgi için.
Düzenleme: Ben her zaman yığına gitmek onlar mistankely cevap vardı . Bu yanlış .
Muhtemelen burada bir şey kaçırıyorum ama tahsisi neden önemsiyoruz?
Değer türleri değere göre aktarılır;) ve dolayısıyla tanımlandıkları yerden farklı bir kapsamda mutasyona uğratılamazlar. Değeri değiştirebilmek için [ref] anahtar sözcüğünü eklemeniz gerekir.
Referans tipleri referans ile geçirilir ve mutasyona uğrayabilir.
Elbette en popüler olan değişmez referans türleri dizeleri vardır.
Dizi düzeni / başlatma: Değer türleri -> sıfır bellek [ad, zip] [ad, zip] Referans türleri -> sıfır bellek -> boş [ref] [ref]
A class
veya struct
bildirim, çalışma zamanında örnekler veya nesneler oluşturmak için kullanılan bir taslak gibidir. Eğer bir tanımlarsanız class
veya struct
Kişi denilen Kişi tip adıdır. Kişi türünde bir p değişkenini bildirir ve başlatırsanız, p'nin Kişi nesnesi veya örneği olduğu söylenir. Aynı Kişi tip birden çok örneği oluşturulabilir ve her bir örnek onun farklı değerlere sahip olabilir properties
ve fields
.
A class
bir referans türüdür. Öğesinin bir nesnesi class
oluşturulduğunda, nesnenin atandığı değişken yalnızca bu belleğe bir başvuru içerir. Nesne başvurusu yeni bir değişkene atandığında, yeni değişken orijinal nesneyi ifade eder. Bir değişken üzerinden yapılan değişiklikler diğer değişkene yansıtılır çünkü ikisi de aynı verilere karşılık gelir.
A struct
bir değer türüdür. A struct
oluşturulduğunda, struct
atandığı değişken yapının gerçek verilerini tutar. Ne zaman struct
yeni bir değişkene atanır, bu kopyalanır. Bu nedenle yeni değişken ve orijinal değişken aynı verilerin iki ayrı kopyasını içerir. Bir kopyada yapılan değişiklikler diğer kopyayı etkilemez.
Genel olarak, classes
daha karmaşık davranışları veya bir class
nesne oluşturulduktan sonra değiştirilmesi amaçlanan verileri modellemek için kullanılır . Structs
en çok struct
, oluşturulduktan sonra değiştirilmesi amaçlanmayan verileri içeren küçük veri yapıları için uygundur .
Hemen hemen Değer türleri olarak kabul edilen yapılar yığına ayrılırken, nesneler yığın üzerinde, nesne başvurusu (işaretçi) yığına ayrılır.
Yapılar yığına ayrılır. İşte size yardımcı bir açıklama:
Ayrıca, .NET içinde örnekleme yapıldığında sınıflar bellekte yığın veya .NET'in ayrılmış bellek alanına ayrılır. Oysa yapılar, yığındaki tahsis nedeniyle somutlaştırıldığında daha fazla verim sağlar. Ayrıca, yapılar içindeki geçiş parametrelerinin değere göre yapıldığına dikkat edilmelidir.