Diziler, yığın ve yığın ve değer türleri


134
int[] myIntegers;
myIntegers = new int[100];

Yukarıdaki kodda, yeni int [100] öbek üzerinde dizi oluşturuyor mu? CLR'de c # ile okuduğumdan cevap evet. Ama anlayamıyorum, dizi içindeki gerçek int ne olur. Değer türleri olduklarından, örneğin, myIntegers'ı programın diğer bölümlerine aktarabildiğim için kutulu olmaları gerekirdi ve her zaman üzerinde bırakılırlarsa yığını dağıtırlardı . Yoksa yanılıyor muyum? Sanırım sadece kutulu olurdu ve dizi var olduğu sürece yığın üzerinde yaşayacaktı.

Yanıtlar:


289

Diziniz öbek üzerinde ayrılmıştır ve girişler kutulu değildir.

Karışıklığınızın kaynağı büyük olasılıkla, insanlar yığın üzerinde referans türlerinin ve yığın üzerinde değer türlerinin ayrıldığını söylemişlerdir. Bu tamamen doğru bir temsil değildir.

Tüm yerel değişkenler ve parametreler yığına ayrılır. Bu, hem değer türlerini hem de referans türlerini içerir. İkisi arasındaki fark sadece değişkende saklanan şeydir . Şaşırtıcı olmayan bir şekilde, bir değer türü için, türün değeri doğrudan değişkende saklanır ve bir referans türü için, türün değeri yığın üzerinde depolanır ve bu değere bir başvuru , değişkente depolanan değerdir.

Aynı şey alanlar için de geçerlidir. Birleştirilmiş türden (a classveya a struct) bir örnek için bellek ayrıldığında, her örnek alanı için depolama alanı içermelidir. Başvuru türü alanlar için, bu depolama alanı, değerin kendisinden sonradan ayrılacak değere bir başvuru içerir. Değer türü alanlar için bu depolama alanı gerçek değeri tutar.

Yani, aşağıdaki tipler verildiğinde:

class RefType{
    public int    I;
    public string S;
    public long   L;
}

struct ValType{
    public int    I;
    public string S;
    public long   L;
}

Bu türlerin her birinin değerleri 16 bayt bellek gerektirir (32 bit kelime boyutu olduğu varsayılarak). Alan Iher bir durumda değerini depolamak için 4 bayt alır, alan Solarak referans olarak saklamak için 4 bayt alır ve alan Ldeğerini depolamak için 8 bayt alır. Yani her ikisinin değeri için hafıza RefTypeve ValTypeşuna benzer:

 0 ┌───────────────────┐
   │ Ben │
 4 ├───────────────────┤
   │ S │
 8 ├───────────────────┤
   │ L │
   │ │
16 └───────────────────┘

Şimdi türleri, bir işlevde üç yerel değişkenler olsaydı RefType, ValTypeve int[]bu gibi:

RefType refType;
ValType valType;
int[]   intArray;

o zaman yığınız şöyle görünebilir:

 0 ┌───────────────────┐
   │ refType │
 4 ├───────────────────┤
   │ valType │
   │ │
   │ │
   │ │
20 ├───────────────────┤
   │ intArray │
24 └───────────────────┘

Bu yerel değişkenlere değerler atadıysanız, şöyle:

refType = new RefType();
refType.I = 100;
refType.S = "refType.S";
refType.L = 0x0123456789ABCDEF;

valType = new ValType();
valType.I = 200;
valType.S = "valType.S";
valType.L = 0x0011223344556677;

intArray = new int[4];
intArray[0] = 300;
intArray[1] = 301;
intArray[2] = 302;
intArray[3] = 303;

Sonra yığınınız şöyle görünebilir:

 0 ┌───────────────────┐
   │ 0x4A963B68 │ - refType'ın yığın adresi
 4 ├───────────────────┤
   │ 200 │ - "valType.I` değeri
   │ 0x4A984C10 │ - "valType.S" öğesinin yığın adresi
   │ 0x44556677 │ - düşük 32 bitlik "valType.L"
   │ 0x00112233 │ - yüksek 32 bitlik "valType.L"
20 ├───────────────────┤
   │ 0x4AA4C288 │ - "intArray" öğesinin yığın adresi
24 └───────────────────┘

Adresindeki 0x4A963B68(değeri refType) bellek şuna benzer:

 0 ┌───────────────────┐
   │ 100 │ - `refType.I` değeri
 4 ├───────────────────┤
   │ 0x4A984D88 │ - `refType.S'nin yığın adresi.
 8 ├───────────────────┤
   │ 0x89ABCDEF │ - düşük 32 bitlik `refType.L`
   │ 0x01234567 │ - yüksek 32 bitlik `refType.L`
16 └───────────────────┘

Adresindeki 0x4AA4C288(değeri intArray) bellek şuna benzer:

 0 ┌───────────────────┐
   │ 4 │ - dizi uzunluğu
 4 ├───────────────────┤
   │ 300 │ - `intArray [0]`
 8 ├───────────────────┤
   1 301 │ - `intArray [1]`
12 ├───────────────────┤
   │ 302 │ - `intArray [2] '
16 ├───────────────────┤
   3 303 │ - `intArray [3] '
20 └───────────────────┘

Şimdi, intArraybaşka bir fonksiyona geçtiyseniz , yığına itilen değer 0x4AA4C288, dizinin bir kopyası değil , dizinin adresi olacaktır .


52
Tüm yerel değişkenlerin yığın üzerinde depolandığını belirten ifadenin yanlış olduğunu unutmayın. Anonim bir işlevin dış değişkenleri olan yerel değişkenler öbek üzerinde depolanır. Yineleyici blokların yerel değişkenleri yığın üzerinde saklanır. Zaman uyumsuz blokların yerel değişkenleri yığın üzerinde saklanır. Kayıtlı olan yerel değişkenler ne yığında ne de yığınta depolanır. Ortaya çıkan yerel değişkenler ne yığında ne de yığın üzerinde saklanır.
Eric Lippert

5
LOL, her zaman nit seçici, Bay Lippert. :) Son iki durum dışında, sözde "yerliler" derleme zamanında yerli olmaktan vazgeçmek zorunda hissediyorum. Uygulama, onları sınıf üyelerinin durumuna yükseltir, bu da yığınta depolanmalarının tek sebebidir. Yani bu sadece bir uygulama detayı (keskin nişancı). Tabii ki, kayıt depolaması daha da düşük bir uygulama detayıdır ve seçim sayılmaz.
P Daddy

3
Tabii ki, tüm yazım uygulama ayrıntıları, ancak, bildiğiniz gibi, hepsi değişkenler ve değerler kavramlarını ayırmaya çalışıyordu . Bir değişken (buna yerel, alan, parametre, ne olursa olsun) yığın, yığın veya başka bir uygulama tanımlı yerde depolanabilir, ancak önemli olan bu değildir. Önemli olan, bu değişkenin temsil ettiği değeri doğrudan mı yoksa başka bir yerde saklanan o değere bir referans mı olduğudır. Bu, kopya semantiğini etkilediği için önemlidir: bu değişkenin kopyalanmasının değerini veya adresini kopyalayıp kopyalamadığı.
P Daddy

16
Görünüşe göre benden daha "yerel değişken" olmanın ne demek olduğu konusunda farklı bir fikriniz var. Bir "yerel değişken" in uygulama ayrıntıları ile karakterize edildiğine inanıyorsunuz . Bu inanç, C # belirtiminde farkında olduğum bir şeyle haklı değil. Yerel bir değişken aslında kimin bloğu içinde bildirilen bir değişkendir adı olan kapsamında sadece engellemeyle alakalı beyan alanı boyunca. Bir uygulama detayı olarak, bir kapatma sınıfının alanlarına çekilen yerel değişkenlerin hala C # kurallarına göre yerel değişkenler olduğunu garanti ederim .
Eric Lippert

15
Bununla birlikte, cevabınız genellikle mükemmel; değerlerin kavramsal olarak değişkenlerden farklı olduğu nokta, temel olduğu için mümkün olduğunca sık ve yüksek sesle yapılması gereken noktadır . Ve yine de birçok insan onlarla ilgili garip efsanelere inanıyor! İyi dövüşle savaştığın için sana çok iyi.
Eric Lippert

23

Evet, dizi öbek üzerinde bulunacaktır.

Dizi içindeki girişler kutulu olmayacaktır. Öbek üzerinde bir değer türü bulunması, mutlaka kutunun gönderileceği anlamına gelmez. Boks, yalnızca type nesnesi başvurusuna int gibi bir değer türü atandığında gerçekleşir.

Örneğin

Kutu yok:

int i = 42;
myIntegers[0] = 42;

Kutular:

object i = 42;
object[] arr = new object[10];  // no boxing here 
arr[0] = 42;

Eric'in bu konudaki gönderisine de göz atmak isteyebilirsiniz:


1
Ama anlamıyorum. Yığın üzerinde değer türleri ayrılmamalı mı? Ya da hem değer hem de referans türleri hem yığın hem de yığın üzerinde tahsis edilebilir ve sadece bir yerde mi yoksa sadece bir yerde mi saklanır?
devoured elysium

4
@Jorge, referans tipi sarıcı / kapsayıcı içermeyen bir değer türü yığın üzerinde yaşar. Bununla birlikte, bir referans tipi kap içinde kullanıldığında, yığın içinde yaşar. Dizi başvuru tipidir ve bu nedenle int için bellek yığının içinde olmalıdır.
JaredPar

2
@Jorge: referans türleri sadece yığınta yaşar, asla yığın üzerinde yaşamaz. Aksine, bir işaretçiyi bir yığın konumuna bir referans türünün bir nesnesine depolamak (doğrulanabilir kodda) mümkün değildir.
Anton Tykhyy

1
Sanırım arr'ye i atamak istediniz [0]. Sürekli atama yine de "42"
boksuna

@AntonTykhyy: Bir CLR'nin kaçış analizi yapamayacağını söyleyen bir kural yok. Bir nesneye, onu oluşturan işlevin ömrü boyunca hiçbir zaman başvurulmayacağını algılarsa, bir değer türü olsun ya da olmasın, nesneyi yığın üzerinde oluşturmak tamamen meşrudur ve hatta tercih edilir. "Değer türü" ve "referans türü" temel olarak değişkenin bellekte ne olduğunu açıklar, nesnenin nerede yaşadığına dair sert ve hızlı bir kural değil.
cHao

21

Neler olduğunu anlamak için bazı gerçekler:

  • Nesne her zaman öbek üzerinde tahsis edilir.
  • Yığın yalnızca nesneler içerir.
  • Değer türleri yığın üzerinde veya öbek üzerindeki bir nesnenin bir parçası olarak ayrılır.
  • Dizi bir nesnedir.
  • Bir dizi yalnızca değer türleri içerebilir.
  • Nesne başvurusu bir değer türüdür.

Bu nedenle, bir tamsayı diziniz varsa, dizi öbek üzerinde ayrılır ve içerdiği tamsayılar öbek üzerindeki dizi nesnesinin bir parçasıdır. Tamsayılar, öbek üzerindeki dizi nesnesinin içinde bulunur, ayrı nesneler olarak değil, bu nedenle kutulu değildir.

Bir dizi dizeniz varsa, bu gerçekten bir dizi dizi başvurusudur. Referanslar değer türleri olduğundan öbek üzerindeki dizi nesnesinin bir parçası olurlar. Diziye bir dize nesnesi koyarsanız, dizideki dize nesnesine başvuruyu koyarsınız ve dize öbek üzerinde ayrı bir nesnedir.


Evet, referanslar tam olarak değer türleri gibi davranır, ancak genellikle bu şekilde adlandırılmadığını veya değer türlerine dahil edilmediğini fark ettim. Örneğin bakınız (ancak bunun gibi daha pek çok şey vardır) msdn.microsoft.com/en-us/library/s1ax56ch.aspx
Henk Holterman

@Henk: Evet, referansların değer türü değişkenleri arasında listelenmediği konusunda haklısınız, ancak belleğin onlar için nasıl tahsis edildiği söz konusu olduğunda, her açıdan değer türündedirler ve bunu bellek ayırmanın nasıl olduğunu anlamak çok yararlıdır hepsi birbirine uyuyor. :)
Guffa

5. nokta, "Bir dizi sadece değer türleri içerebilir." Dize dizisi ne olacak? string [] strings = yeni dize [4];
Sunil Purushothaman

9

Sorunuzun özünde referans ve değer türleri hakkında bir yanlış anlaşılma olduğunu düşünüyorum. Bu muhtemelen her .NET ve Java geliştiricisinin uğraştığı bir şeydir.

Dizi sadece bir değer listesidir. Bir referans türünün (a diyelim) bir string[]dizisiyse, o dizi, stringöbek üzerindeki çeşitli nesnelere yapılan başvuruların bir listesidir , çünkü referans bir referans türünün değeridir . Dahili olarak, bu referanslar bellekteki bir adresin göstergeleri olarak uygulanır. Bunu görselleştirmek isterseniz, böyle bir dizi bellekte (öbekte) şöyle görünür:

[ 00000000, 00000000, 00000000, F8AB56AA ]

Bu, öbek üzerindeki nesnelere string4 referans içeren bir dizidir string(buradaki sayılar onaltılıktır). Şu anda, yalnızca son stringaslında bir şeye işaret ediyor (bellek tahsis edildiğinde tüm sıfırlara başlatılır), bu dizi temel olarak C # 'daki bu kodun sonucu olacaktır:

string[] strings = new string[4];
strings[3] = "something"; // the string was allocated at 0xF8AB56AA by the CLR

Yukarıdaki dizi 32 bitlik bir programda olacaktır. 64 bitlik bir programda, referanslar iki kat daha büyük F8AB56AAolacaktır ( olurdu 00000000F8AB56AA).

Eğer değer türleri dizisi varsa (bir demek int[]olduğu daha sonra dizi sayının bir liste vardır) değeri bir değer türü olduğu değeri kendisi (Bu nedenle adı). Böyle bir dizinin görselleştirilmesi şu şekildedir:

[ 00000000, 45FF32BB, 00000000, 00000000 ]

Bu 4 tamsayıdan oluşan bir dizidir, burada sadece ikinci int'e bir değer atanır (1174352571'e, bu onaltılık sayının ondalık gösterimidir) ve tamsayıların geri kalanı 0 olur (dediğim gibi, bellek sıfıra başlatılır) ve onaltılık olarak 00000000 ondalık olarak 0'dır). Bu diziyi üreten kod şöyle olacaktır:

 int[] integers = new int[4];
 integers[1] = 1174352571; // integers[1] = 0x45FF32BB would be valid too

Bu int[]dizi de öbek üzerinde saklanır.

Başka bir örnek olarak, bir short[4]dizinin belleği şöyle görünür:

[ 0000, 0000, 0000, 0000 ]

Şöyle değeri a shortbir 2 byte kadardır.

Bir değer türünün saklandığı yerde, Eric Lippert'in burada çok iyi açıkladığı gibi , değer ve referans türleri (davranıştaki farktır) arasındaki farkların doğasında olmayan bir uygulama detayıdır .

Eğer bir yönteme şey geçirdiğinizde sonra (bir başvuru türü veya bir değer türü olduğunu olun) kopya ait değerin türü aslında yönteme geçirilir. Bir referans türü söz konusu olduğunda, değer bir referanstır (bunu bir uygulama detayı olmasına rağmen bir bellek parçasına işaretçi olarak düşünün) ve bir değer türü söz konusu olduğunda değer, nesnenin kendisidir.

// Calling this method creates a copy of the *reference* to the string
// and a copy of the int itself, so copies of the *values*
void SomeMethod(string s, int i){}

Boks yalnızca bir değer türünü referans türüne dönüştürürseniz oluşur . Bu kod kutuları:

object o = 5;

"Bir uygulama detayı" yazı tipi boyutunda olması gerektiğine inanıyorum: 50 piksel. ;)
sisve

2

Bunlar @P Daddy tarafından verilen cevabı gösteren resimlerdir.

resim açıklamasını buraya girin

resim açıklamasını buraya girin

Ve kendi tarzımdaki ilgili içerikleri gösterdim.

resim açıklamasını buraya girin


@P Daddy Çizimler yaptım. Lütfen yanlış kısım olup olmadığını kontrol edin. Başka sorularım var. 1. 4 uzunluk int tipi dizi oluşturduğumda, uzunluk bilgisi (4) de her zaman bellekte saklanır mı?
YoungMin Park

2. İkinci örnekte, kopyalanan dizi adresi nerede saklanır? İntArray adresinin depolandığı yığın alanı mı? Başka bir yığın değil, aynı tür bir yığın mı? Farklı türde bir yığın mı? 3. Düşük 32 bit / yüksek 32 bit ne anlama gelir? 4. Yeni anahtar sözcük kullanarak yığına değer türü (bu örnekte yapı) ayırdığımda dönüş değeri nedir? Adres de mi? Bu ifade Console.WriteLine (valType) tarafından kontrol ederken, ConsoleApp.ValType gibi nesne gibi tam nitelenmiş adı gösterirdi.
YoungMin Park

5. valType.I = 200; Bu ifade ben valType adresini almak anlamına mı geliyor, bu adresle ben erişim I ve orada 200 depolamak "yığın".
YoungMin Park

1

Öbek üzerinde bir tamsayı dizisi ayrılmıştır, başka bir şey, hiçbir şey daha az değildir. myIntegers, girişlerin tahsis edildiği bölümün başlangıcına başvurur. Bu başvuru yığında bulunur.

Yığın üzerinde bulunan Nesne türü gibi myObjects [] gibi bir dizi başvuru türü nesneniz varsa, nesnelerin kendilerine başvuruda bulunan değer demetine başvurur.

Özetlemek gerekirse, myIntegers'ı bazı işlevlere iletirseniz, referansı yalnızca gerçek tamsayı demetinin atandığı yere iletirsiniz.


1

Örnek kodunuzda boks yok.

Değer türleri, yığın dizilerinizde olduğu gibi yığın üzerinde yaşayabilir. Dizi öbek üzerinde tahsis edilir ve değer türleri olan ints depolar. Dizinin içeriği sıfır (sıfır) olarak varsayılan (int) olarak başlatılır.

Değer türü içeren bir sınıfı düşünün:


    class HasAnInt
    {
        int i;
    }

    HasAnInt h = new HasAnInt();

Değişken h, yığın üzerinde yaşayan bir HasAnInt örneğini ifade eder. Sadece bir değer türü içerir. Bu gayet iyi, 'ben' sadece bir sınıfta olduğu gibi yığın üzerinde yaşıyor. Bu örnekte de boks yoktur.


1

Yeterli herkes tarafından söylendi, ancak birisi yığın, yığın, yerel değişkenler ve statik değişkenler hakkında açık (ancak resmi olmayan) bir örnek ve belge arıyorsa, Jon Skeet'in .NET'teki Bellek hakkındaki makalesine bakın - ne olur nerede

Alıntı:

  1. Her yerel değişken (yani bir yöntemde bildirilen) yığına kaydedilir. Bu, başvuru türü değişkenleri içerir - değişkenin kendisi yığın üzerindedir, ancak bir başvuru türü değişkeninin değerinin nesnenin kendisi değil yalnızca bir başvuru (veya null) olduğunu unutmayın. Yöntem parametreleri de yerel değişkenler olarak sayılır, ancak ref değiştiricisiyle bildirilirlerse, kendi yuvalarını almazlar, ancak çağrı kodunda kullanılan değişkenle bir yuva paylaşırlar. Daha fazla ayrıntı için parametre aktarma ile ilgili makaleme bakın.

  2. Bir başvuru türü için örnek değişkenleri her zaman öbek üzerindedir. Nesnenin kendisi burada "yaşar".

  3. Bir değer türü için örnek değişkenleri, değer türünü bildiren değişkenle aynı bağlamda depolanır. Örneğin için bellek yuvası, örnek içindeki her alan için yuvaları etkin bir şekilde içerir. Bu, (önceki iki nokta göz önüne alındığında), bir yöntem içinde bildirilen bir yapı değişkeninin her zaman yığın üzerinde olacağı anlamına gelirken, bir sınıfın örnek alanı olan bir yapı değişkeni yığın üzerinde olacağı anlamına gelir.

  4. Her statik değişken, başvuru türü veya değer türü içinde bildirilmiş olmasına bakılmaksızın yığın üzerinde depolanır. Kaç örnek oluşturulursa oluşturulsun toplamda yalnızca bir yuva vardır. (Yine de, o yuvanın var olması için herhangi bir örnek oluşturmaya gerek yoktur.) Değişkenlerin tam olarak hangi yığın üzerinde yaşandığının ayrıntıları karmaşıktır, ancak konuyla ilgili bir MSDN makalesinde ayrıntılı olarak açıklanmıştır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.