Yapı hizalaması neden bir alan türünün ilkel veya kullanıcı tanımlı olmasına bağlıdır?


121

In Noda Zaman v2, biz nanosaniye çözünürlüğü taşınıyoruz. Bu, ilgilendiğimiz tüm zaman aralığını temsil etmek için artık 8 baytlık bir tamsayı kullanamayacağımız anlamına geliyor. Bu, beni Noda Time'ın (birçok) yapısının bellek kullanımını araştırmaya sevk etti ve bu da beni yönlendirdi. CLR'nin uyum kararındaki küçük bir tuhaflığı ortaya çıkarmak için.

İlk olarak, bunun bir uygulama kararı olduğunu ve varsayılan davranışın herhangi bir zamanda değişebileceğini anlıyorum . Ben fark edebilirsiniz kullanarak bu maddede değişiklikler [StructLayout]ve [FieldOffset], ama yerine mümkünse o gerektirmeyen bir çözüm gelirdi.

Benim temel senaryom, structbir başvuru türü alanı ve bu alanların basit sarmalayıcılar olduğu diğer iki değer türü alanını içeren bir alanımın olmasıdır int. Bunun 64-bit CLR'de 16 bayt olarak gösterileceğini ummuştum (referans için 8 ve diğerlerinin her biri için 4), ancak nedense 24 bayt kullanıyor. Bu arada, dizileri kullanarak alanı ölçüyorum - farklı durumlarda düzenin farklı olabileceğini anlıyorum, ancak bu mantıklı bir başlangıç ​​noktası gibi geldi.

İşte sorunu gösteren örnek bir program:

using System;
using System.Runtime.InteropServices;

#pragma warning disable 0169

struct Int32Wrapper
{
    int x;
}

struct TwoInt32s
{
    int x, y;
}

struct TwoInt32Wrappers
{
    Int32Wrapper x, y;
}

struct RefAndTwoInt32s
{
    string text;
    int x, y;
}

struct RefAndTwoInt32Wrappers
{
    string text;
    Int32Wrapper x, y;
}    

class Test
{
    static void Main()
    {
        Console.WriteLine("Environment: CLR {0} on {1} ({2})",
            Environment.Version,
            Environment.OSVersion,
            Environment.Is64BitProcess ? "64 bit" : "32 bit");
        ShowSize<Int32Wrapper>();
        ShowSize<TwoInt32s>();
        ShowSize<TwoInt32Wrappers>();
        ShowSize<RefAndTwoInt32s>();
        ShowSize<RefAndTwoInt32Wrappers>();
    }

    static void ShowSize<T>()
    {
        long before = GC.GetTotalMemory(true);
        T[] array = new T[100000];
        long after  = GC.GetTotalMemory(true);        
        Console.WriteLine("{0}: {1}", typeof(T),
                          (after - before) / array.Length);
    }
}

Ve dizüstü bilgisayarımdaki derleme ve çıktı:

c:\Users\Jon\Test>csc /debug- /o+ ShowMemory.cs
Microsoft (R) Visual C# Compiler version 12.0.30501.0
for C# 5
Copyright (C) Microsoft Corporation. All rights reserved.


c:\Users\Jon\Test>ShowMemory.exe
Environment: CLR 4.0.30319.34014 on Microsoft Windows NT 6.2.9200.0 (64 bit)
Int32Wrapper: 4
TwoInt32s: 8
TwoInt32Wrappers: 8
RefAndTwoInt32s: 16
RefAndTwoInt32Wrappers: 24

Yani:

  • Bir referans türü alanınız yoksa, CLR Int32Wrapperalanları bir arada paketlemekten memnuniyet duyar ( TwoInt32Wrappersboyutu 8'dir)
  • Bir referans türü alanı olsa bile, CLR intalanları birlikte paketlemekten mutludur ( RefAndTwoInt32sboyutu 16'dır)
  • İkisini birleştirdiğinde, her Int32Wrapperalan 8 bayta doldurulmuş / hizalanmış gibi görünür. ( RefAndTwoInt32Wrappers24 bedene sahiptir.)
  • Hata ayıklayıcıda aynı kodu çalıştırmak (ancak yine de bir sürüm derlemesi) 12 boyutunu gösterir.

Diğer birkaç deney de benzer sonuçlar verdi:

  • Referans türü alanını değer türü alanlarının sonrasına koymak yardımcı olmuyor
  • objectBunun yerine kullanmak stringyardımcı olmuyor ("herhangi bir referans türü" olduğunu umuyorum)
  • Referans etrafında "sarmalayıcı" olarak başka bir yapıyı kullanmak yardımcı olmaz
  • Referans etrafında bir sarmalayıcı olarak genel bir yapı kullanmak yardımcı olmaz
  • Alan eklemeye devam edersem (basitlik için çiftler halinde) intalanlar hala 4 bayt, Int32Wrapperalanlar 8 bayt olarak sayılır
  • [StructLayout(LayoutKind.Sequential, Pack = 4)]Görünürdeki her yapıya eklemek sonuçları değiştirmez

Herkes paketlenecek olan (ideal referans belgelerle) Bunun için herhangi bir açıklama veya ben alanlarını istediğinizi CLR'ye ipucu elde nasıl bir öneri var mı olmadan ofset sabit alanını belirterek?


1
Aslında kullanıyor gibi görünmüyorsunuz, bunun yerine Ref<T>kullanıyorsunuz, stringbunun bir fark yaratması gerektiği anlamına gelmiyor.
tvanfosson

2
İki koyarsanız iki TwoInt32Wrappersveya bir Int64ve bir ile bir yapı oluşturursanız ne olur TwoInt32Wrappers? Bir jenerik Pair<T1,T2> {public T1 f1; public T2 f2;}oluşturup ardından Pair<string,Pair<int,int>>ve oluştursanız nasıl olur Pair<string,Pair<Int32Wrapper,Int32Wrapper>>? Hangi kombinasyonlar JITter'i bir şeyleri doldurmaya zorlar?
supercat

7
@supercat: Kendiniz için kod ve denemeyi kopyalamak için Muhtemelen en iyisi - ama Pair<string, TwoInt32Wrappers> yok sadece 16 bayt vermek konusu ele alınacak, böylece. Büyüleyici.
Jon Skeet

9
@SLaks: Bazen bir yapı yerel koda aktarıldığında, Çalışma Zamanı tüm verileri farklı bir düzene sahip bir yapıya kopyalar. Marshal.SizeOf.NET kodundaki yapının boyutuyla herhangi bir ilişkisi olması gerekmeyen yerel koda geçirilecek yapının boyutunu döndürür.
supercat

5
İlginç gözlem: Mono doğru sonuçlar veriyor. Çevre: CLR 4.0.30319.17020 Unix 3.13.0.24 (64 bit) üzerinde Int32Wrapper: 4 TwoInt32s: 8 TwoInt32Wrapper: 8 RefAndTwoInt32s: 16 RefAndTwoInt32Wrappers: 16
AndreyAkinshin

Yanıtlar:


85

Bunun bir hata olduğunu düşünüyorum. Otomatik düzenin yan etkisini görüyorsunuz, önemsiz olmayan alanları 64 bit modunda 8 bayt katı olan bir adrese hizalamayı seviyor. [StructLayout(LayoutKind.Sequential)]Özniteliği açıkça uyguladığınızda bile oluşur . Bunun olmaması gerekiyor.

Yapı üyelerini herkese açık hale getirerek ve aşağıdaki gibi test kodunu ekleyerek görebilirsiniz:

    var test = new RefAndTwoInt32Wrappers();
    test.text = "adsf";
    test.x.x = 0x11111111;
    test.y.x = 0x22222222;
    Console.ReadLine();      // <=== Breakpoint here

Kesme noktası çarptığında, Debug + Windows + Bellek + Bellek 1'i kullanın. 4 baytlık tam sayılara geçin &testve Adres alanına girin:

 0x000000E928B5DE98  0ed750e0 000000e9 11111111 00000000 22222222 00000000 

0xe90ed750e0benim makinemdeki dize göstericidir (sizin değil). Int32WrappersBoyutu 24 bayta dönüştüren ekstra 4 baytlık dolgu ile kolayca görebilirsiniz . Yapıya geri dönün ve dizeyi en sona koyun. Tekrarlayın ve dize işaretçisinin hala ilk olduğunu göreceksiniz . İhlal LayoutKind.Sequentialediyorsun LayoutKind.Auto.

Microsoft'u bunu düzeltmeye ikna etmek zor olacak, bu şekilde çok uzun süre çalıştı, bu yüzden herhangi bir değişiklik bir şeyleri bozacak . CLR yalnızca [StructLayout]bir yapının yönetilen sürümünü onurlandırmaya ve onu kırılgan hale getirmeye çalışır, genel olarak çabucak pes eder. DateTime içeren herhangi bir yapı için bilinir. Gerçek LayoutKind garantisini yalnızca bir yapıyı sıralarken alırsınız. Sıralanmış sürüm, Marshal.SizeOf()size söyleyeceği gibi kesinlikle 16 bayttır .

Kullanılması LayoutKind.Explicitduymak istediğini değil, düzelir.


7
"Microsoft'u bunu düzeltmeye ikna etmek zor olacak, bu şekilde çok uzun süre çalıştı, bu yüzden herhangi bir değişiklik bir şeyi bozacak." Bunun 32 bit veya mono olarak tezahür etmemesi yardımcı olabilir (diğer yorumlara göre).
NPSF3000

StructLayoutAttribute dokümantasyonu oldukça ilginçtir. Temel olarak, yönetilen bellekte StructLayout aracılığıyla yalnızca blittable türler denetlenir. İlginç, bunu hiç bilmiyordum.
Michael Stum

@Soner hayır düzeltmez. Düzeni her iki alana da 8 ofset olacak şekilde koydunuz mu? Eğer öyleyse, x ve y aynıdır ve birini değiştirmek diğerini değiştirir. Açıkça Jon'un peşinde olduğu değil.
BartoszAdamczewski

stringBaşvurulan başka bir yeni referans türü ( class) ile ikame [StructLayout(LayoutKind.Sequential)]edilmesi hiçbir şeyi değiştirmiyor gibi görünmektedir. Ters yönde uygulayarak [StructLayout(LayoutKind.Auto)]için struct Int32Wrapperbellek kullanımı değiştirir TwoInt32Wrappers.
Jeppe Stig Nielsen

1
"Microsoft'u bunu düzeltmeye ikna etmek zor olacak, bu şekilde çok uzun süre çalıştı, bu yüzden herhangi bir değişiklik bir şeyi bozacak." xkcd.com/1172
iCodeSometime

19

EDIT2

struct RefAndTwoInt32Wrappers
{
    public int x;
    public string s;
}

Bu kod 8 bayt hizalı olacak, böylece yapı 16 bayta sahip olacak. Buna kıyasla:

struct RefAndTwoInt32Wrappers
{
    public int x,y;
    public string s;
}

4 bayt hizalı olacağı için bu yapı da 16 bayta sahip olacaktır. Dolayısıyla buradaki mantık, CLR'deki yapı uyumunun en çok hizalanan alanların sayısı ile belirlendiğidir, sınıflar bunu yapamazlar, bu nedenle 8 bayt hizalı kalırlar.

Şimdi tüm bunları birleştirir ve yapı oluşturursak:

struct RefAndTwoInt32Wrappers
{
    public int x,y;
    public Int32Wrapper z;
    public string s;
}

24 bayta sahip olacaktır {x, y} her biri 4 bayta ve {z, s} 8 bayta sahip olacaktır. Yapıda bir ref türü sunduğumuzda, CLR her zaman özel yapımızı sınıf hizalamasına uyacak şekilde hizalayacaktır.

struct RefAndTwoInt32Wrappers
{
    public Int32Wrapper z;
    public long l;
    public int x,y;  
}

Int32Wrapper aynı uzunlukta hizalanacağından bu kod 24 bayta sahip olacaktır. Bu nedenle, özel yapı sarmalayıcı her zaman yapıdaki en yüksek / en iyi hizalanmış alana veya kendi dahili en önemli alanlarına hizalanacaktır. Dolayısıyla, 8 bayt hizalı bir ref dizesi olması durumunda, yapı sarmalayıcısı buna hizalanacaktır.

Yapının içindeki özel yapı alanının sonlandırılması, her zaman yapıdaki en yüksek hizalanmış örnek alanıyla hizalanacaktır. Şimdi bunun bir hata olup olmadığından emin değilsem, ancak bazı kanıtlar olmadan bunun bilinçli bir karar olabileceği fikrime bağlı kalacağım.


DÜZENLE

Boyutlar aslında yalnızca bir yığın üzerine tahsis edildiğinde doğrudur, ancak yapıların kendileri daha küçük boyutlara sahiptir (alanlarının tam boyutları). Bunun CLR kodunda bir hata olabileceğini, ancak kanıtlarla desteklenmesi gerektiğini öne süren ek analizler.

Yararlı bir şey bulunursa, cli kodunu inceleyeceğim ve daha fazla güncelleme göndereceğim.


Bu, .NET mem ayırıcısı tarafından kullanılan bir hizalama stratejisidir.

public static RefAndTwoInt32s[] test = new RefAndTwoInt32s[1];

static void Main()
{
    test[0].text = "a";
    test[0].x = 1;
    test[0].x = 1;

    Console.ReadKey();
}

X64 altında .net40 ile derlenen bu kod, WinDbg'de aşağıdakileri yapalım:

Önce Yığın üzerindeki türü bulalım:

    0:004> !dumpheap -type Ref
       Address               MT     Size
0000000003e72c78 000007fe61e8fb58       56    
0000000003e72d08 000007fe039d3b78       40    

Statistics:
              MT    Count    TotalSize Class Name
000007fe039d3b78        1           40 RefAndTwoInt32s[]
000007fe61e8fb58        1           56 System.Reflection.RuntimeAssembly
Total 2 objects

Elimize geçtiğinde, bu adresin altında ne olduğunu görelim:

    0:004> !do 0000000003e72d08
Name:        RefAndTwoInt32s[]
MethodTable: 000007fe039d3b78
EEClass:     000007fe039d3ad0
Size:        40(0x28) bytes
Array:       Rank 1, Number of elements 1, Type VALUETYPE
Fields:
None

Bunun bir ValueType olduğunu ve bizim yarattığımız olduğunu görüyoruz. Bu bir dizi olduğundan, dizideki tek bir öğenin ValueType def'ini almamız gerekir:

    0:004> !dumparray -details 0000000003e72d08
Name:        RefAndTwoInt32s[]
MethodTable: 000007fe039d3b78
EEClass:     000007fe039d3ad0
Size:        40(0x28) bytes
Array:       Rank 1, Number of elements 1, Type VALUETYPE
Element Methodtable: 000007fe039d3a58
[0] 0000000003e72d18
    Name:        RefAndTwoInt32s
    MethodTable: 000007fe039d3a58
    EEClass:     000007fe03ae2338
    Size:        32(0x20) bytes
    File:        C:\ConsoleApplication8\bin\Release\ConsoleApplication8.exe
    Fields:
                      MT    Field   Offset                 Type VT     Attr            Value Name
        000007fe61e8c358  4000006        0            System.String      0     instance     0000000003e72d30     text
        000007fe61e8f108  4000007        8             System.Int32      1     instance                    1     x
        000007fe61e8f108  4000008        c             System.Int32      1     instance                    0     y

Yapı aslında 32 bayttır, çünkü 16 baytı doldurma için ayrılmıştır, bu nedenle gerçekte her yapı baştan itibaren en az 16 bayt boyutundadır.

İnts'ten 16 bayt ve bir dize ref: 0000000003e72d18 + 8 bayt EE / padding'e eklerseniz, sonunda 0000000003e72d30 olur ve bu, dize referansı için başlangıç ​​noktasıdır ve tüm referanslar, ilk gerçek veri alanlarından 8 bayt dolguludur. bu, bu yapı için bizim 32 baytımızı oluşturur.

Bakalım dize gerçekten bu şekilde doldurulmuş mu?

0:004> !do 0000000003e72d30    
Name:        System.String
MethodTable: 000007fe61e8c358
EEClass:     000007fe617f3720
Size:        28(0x1c) bytes
File:        C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
String:      a
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
000007fe61e8f108  40000aa        8         System.Int32  1 instance                1 m_stringLength
000007fe61e8d640  40000ab        c          System.Char  1 instance               61 m_firstChar
000007fe61e8c358  40000ac       18        System.String  0   shared           static Empty
                                 >> Domain:Value  0000000001577e90:NotInit  <<

Şimdi yukarıdaki programı aynı şekilde analiz edelim:

public static RefAndTwoInt32Wrappers[] test = new RefAndTwoInt32Wrappers[1];

static void Main()
{
    test[0].text = "a";
    test[0].x.x = 1;
    test[0].y.x = 1;

    Console.ReadKey();
}

0:004> !dumpheap -type Ref
     Address               MT     Size
0000000003c22c78 000007fe61e8fb58       56    
0000000003c22d08 000007fe039d3c00       48    

Statistics:
              MT    Count    TotalSize Class Name
000007fe039d3c00        1           48 RefAndTwoInt32Wrappers[]
000007fe61e8fb58        1           56 System.Reflection.RuntimeAssembly
Total 2 objects

Yapımız şu anda 48 bayt.

0:004> !dumparray -details 0000000003c22d08
Name:        RefAndTwoInt32Wrappers[]
MethodTable: 000007fe039d3c00
EEClass:     000007fe039d3b58
Size:        48(0x30) bytes
Array:       Rank 1, Number of elements 1, Type VALUETYPE
Element Methodtable: 000007fe039d3ae0
[0] 0000000003c22d18
    Name:        RefAndTwoInt32Wrappers
    MethodTable: 000007fe039d3ae0
    EEClass:     000007fe03ae2338
    Size:        40(0x28) bytes
    File:        C:\ConsoleApplication8\bin\Release\ConsoleApplication8.exe
    Fields:
                      MT    Field   Offset                 Type VT     Attr            Value Name
        000007fe61e8c358  4000009        0            System.String      0     instance     0000000003c22d38     text
        000007fe039d3a20  400000a        8             Int32Wrapper      1     instance     0000000003c22d20     x
        000007fe039d3a20  400000b       10             Int32Wrapper      1     instance     0000000003c22d28     y

Burada durum aynıdır, 0000000003c22d18 + 8 bayt dizge ref eklersek, değerin gerçekte bulunduğumuz adresi gösterdiği ilk Int sarmalayıcısının başlangıcında son buluruz.

Şimdi her değerin bir nesne referansı olduğunu görebiliriz, yine 0000000003c22d20'ye göz atarak bunu doğrulayalım.

0:004> !do 0000000003c22d20
<Note: this object has an invalid CLASS field>
Invalid object

Aslında bu doğru bir yapı olduğu için adres bize bunun bir obj veya vt olup olmadığını söylemez.

0:004> !dumpvc 000007fe039d3a20   0000000003c22d20    
Name:        Int32Wrapper
MethodTable: 000007fe039d3a20
EEClass:     000007fe03ae23c8
Size:        24(0x18) bytes
File:        C:\ConsoleApplication8\bin\Release\ConsoleApplication8.exe
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
000007fe61e8f108  4000001        0         System.Int32  1 instance                1 x

Yani gerçekte bu, bu sefer 8 baytı hizalayacak bir Birleşim türü gibidir (tüm doldurmalar ana yapı ile hizalanacaktır). Öyle olmasaydı, 20 bayt ile sonuçlanırdık ve bu optimal değildir, bu yüzden mem ayırıcı bunun olmasına asla izin vermez. Tekrar matematiği yaparsanız, yapının gerçekten 40 bayt boyutunda olduğu ortaya çıkacaktır.

Dolayısıyla, bellek konusunda daha tutucu olmak istiyorsanız, onu asla bir struct özel yapı türünde paketlememelisiniz, bunun yerine basit diziler kullanmalısınız. Başka bir yol da belleği yığın dışı ayırmaktır (örneğin VirtualAllocEx), bu şekilde size kendi bellek bloğunuz verilir ve bunu istediğiniz şekilde yönetirsiniz.

Buradaki son soru, neden birdenbire böyle bir düzen elde edebileceğimizdir. Bir int [] artışının jited kodunu ve performansını struct [] ile karşılaştırırsanız, ikincisi bir birleşim olan 8 bayt hizalı bir adres oluşturacaktır, ancak jite edildiğinde bu daha optimize edilmiş montaj koduna dönüşür (tek LEA ile çoklu MOV karşılaştırması). Bununla birlikte, burada açıklanan durumda performans aslında daha kötü olacaktır, bu yüzden benim düşüncem, bu, birden fazla alana sahip olabilen özel bir tür olduğundan, temeldeki CLR uygulamasıyla tutarlıdır, bu nedenle başlangıç ​​adresini bir yerine koymak daha kolay / daha iyi olabilir. değer (imkansız olacağından) ve orada yapı dolgusu yapın, böylece daha büyük bayt boyutuyla sonuçlanır.


1
Buna kendim bakarsak, boyutu 32 bayt RefAndTwoInt32Wrappers değil , 24'tür ve kodumla bildirilenle aynıdır. Kullanmak yerine bellek görünümüne dumparraybakarsanız ve ayırt edilebilir değerlere sahip (örneğin) 3 öğeli bir dizi için belleğe bakarsanız, her öğenin 8 baytlık bir dize başvurusu ve iki 8 baytlık tamsayıdan oluştuğunu açıkça görebilirsiniz. . dumparrayDeğerlerin nasıl görüntüleneceğini bilmediği için değerleri referans olarak gösterdiğinden şüpheleniyorum Int32Wrapper. Bu "referanslar" kendilerine işaret ediyor; ayrı değerler değiller.
Jon Skeet

1
"16 bayt dolgusu" nu nereden aldığınızdan tam olarak emin değilim, ancak bunun "16 bayt + sayı * öğe boyutu" olacak dizi nesnesinin boyutuna bakmanızdan kaynaklandığından şüpheleniyorum. Yani sayı 2 olan bir dizinin boyutu 72'dir (16 + 2 * 24), ki bunu dumparraygösterir.
Jon Skeet

@jon yapınızı kandırdınız mı ve yığın üzerinde kaç yer kapladığını kontrol ettiniz mi? Normalde dizi boyutu dizinin başlangıcında tutulur, bu da doğrulanabilir.
BartoszAdamczewski

@jon bildirilen boyut aynı zamanda 8'den başlayan dizgenin ofsetini de içeriyor. Dizi öğelerinin çoğu ilk eleman adresinden önce yer aldığı için bu ekstra 8 baytın diziden geldiğini sanmıyorum, ancak iki kez kontrol edeceğim ve buna yorum yapın.
BartoszAdamczewski

1
Hayır, ThreeInt32Wrappers 12 bayt, FourInt32Wrappers 16, FiveInt32Wrappers 20 olarak bitiyor. Düzeni bu kadar sert bir şekilde değiştiren bir referans türü alanının eklenmesiyle ilgili mantıklı bir şey görmüyorum. Ve alanlar türdeyken 8 baytlık hizalamayı göz ardı etmenin oldukça mutlu olduğunu unutmayın Int32. Dürüst olmak gerekirse, yığın üzerinde ne yaptığından pek rahatsız değilim - ama kontrol etmedim.
Jon Skeet

9

Özet Muhtemelen yukarıdaki @ Hans Passant'ın cevabına bakınız. Düzen Sırası çalışmıyor


Bazı testler:

Kesinlikle sadece 64bit'te ve nesne referansı yapıyı "zehirliyor". 32 bit beklediğiniz şeyi yapar:

Environment: CLR 4.0.30319.34209 on Microsoft Windows NT 6.2.9200.0 (32 bit)
ConsoleApplication1.Int32Wrapper: 4
ConsoleApplication1.TwoInt32s: 8
ConsoleApplication1.TwoInt32Wrappers: 8
ConsoleApplication1.ThreeInt32Wrappers: 12
ConsoleApplication1.Ref: 4
ConsoleApplication1.RefAndTwoInt32s: 12
ConsoleApplication1.RefAndTwoInt32Wrappers: 12
ConsoleApplication1.RefAndThreeInt32s: 16
ConsoleApplication1.RefAndThreeInt32Wrappers: 16

Nesne başvurusu eklenir eklenmez tüm yapılar 4 bayt boyutlarından ziyade 8 bayta genişler. Testlerin genişletilmesi:

Environment: CLR 4.0.30319.34209 on Microsoft Windows NT 6.2.9200.0 (64 bit)
ConsoleApplication1.Int32Wrapper: 4
ConsoleApplication1.TwoInt32s: 8
ConsoleApplication1.TwoInt32Wrappers: 8
ConsoleApplication1.ThreeInt32Wrappers: 12
ConsoleApplication1.Ref: 8
ConsoleApplication1.RefAndTwoInt32s: 16
ConsoleApplication1.RefAndTwoInt32sSequential: 16
ConsoleApplication1.RefAndTwoInt32Wrappers: 24
ConsoleApplication1.RefAndThreeInt32s: 24
ConsoleApplication1.RefAndThreeInt32Wrappers: 32
ConsoleApplication1.RefAndFourInt32s: 24
ConsoleApplication1.RefAndFourInt32Wrappers: 40

Gördüğünüz gibi, referans eklenir eklenmez her Int32Wrapper 8 bayt olur, bu nedenle basit hizalama değildir. Farklı şekilde hizalanmış LoH tahsisi olması durumunda dizi tahsisini küçülttüm.


4

Karışıma biraz veri eklemek için - sahip olduklarınızdan bir tür daha oluşturdum:

struct RefAndTwoInt32Wrappers2
{
    string text;
    TwoInt32Wrappers z;
}

Program şöyle yazıyor:

RefAndTwoInt32Wrappers2: 16

Yani TwoInt32Wrappersyapı yeni RefAndTwoInt32Wrappers2yapıda düzgün bir şekilde hizalanmış gibi görünüyor .


64 bit mi çalıştırıyorsun? Hizalama 32
Ben Adams

Bulgularım, çeşitli ortamlar için herkesle aynı.
Jesse C. Slicer
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.