Mengapa perataan struct bergantung pada apakah jenis bidang primitif atau ditentukan pengguna?

121

Di Noda Time v2, kami beralih ke resolusi nanodetik. Itu berarti kita tidak dapat lagi menggunakan integer 8-byte untuk mewakili seluruh rentang waktu yang kita minati. Itu mendorong saya untuk menyelidiki penggunaan memori dari (banyak) struct Waktu Noda, yang pada gilirannya membawa saya untuk mengungkap sedikit keanehan dalam keputusan penyelarasan CLR.

Pertama, saya menyadari bahwa ini adalah keputusan implementasi, dan perilaku default dapat berubah kapan saja. Saya menyadari bahwa saya dapat memodifikasinya menggunakan [StructLayout]dan [FieldOffset], tetapi saya lebih suka menemukan solusi yang tidak memerlukannya jika memungkinkan.

Skenario inti saya adalah bahwa saya memiliki bidang structyang berisi jenis referensi dan dua bidang jenis nilai lainnya, di mana bidang tersebut adalah pembungkus sederhana int. Saya berharap itu akan direpresentasikan sebagai 16 byte pada CLR 64-bit (8 untuk referensi dan 4 untuk masing-masing yang lain), tetapi untuk beberapa alasan itu menggunakan 24 byte. Ngomong-ngomong, saya mengukur ruang menggunakan array - saya mengerti bahwa tata letaknya mungkin berbeda dalam situasi yang berbeda, tetapi ini terasa seperti titik awal yang masuk akal.

Berikut adalah contoh program yang mendemonstrasikan masalah tersebut:

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);
    }
}

Dan kompilasi dan output di laptop saya:

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

Begitu:

  • Jika Anda tidak memiliki bidang tipe referensi, CLR dengan senang hati akan mengemas Int32Wrapperbidang bersama-sama ( TwoInt32Wrappersmemiliki ukuran 8)
  • Bahkan dengan bidang tipe referensi, CLR masih senang untuk mengemas intbidang bersama-sama ( RefAndTwoInt32smemiliki ukuran 16)
  • Menggabungkan keduanya, setiap Int32Wrapperbidang tampak empuk / sejajar dengan 8 byte. ( RefAndTwoInt32Wrappersmemiliki ukuran 24.)
  • Menjalankan kode yang sama di debugger (tetapi masih berupa rilis build) menunjukkan ukuran 12.

Beberapa eksperimen lain memberikan hasil yang serupa:

  • Menempatkan bidang tipe referensi setelah bidang tipe nilai tidak membantu
  • Menggunakan objectbukannya stringtidak membantu (saya rasa ini adalah "jenis referensi apa pun")
  • Menggunakan struct lain sebagai "pembungkus" di sekitar referensi tidak membantu
  • Menggunakan struct generik sebagai pembungkus di sekitar referensi tidak membantu
  • Jika saya terus menambahkan bidang (berpasangan untuk kesederhanaan), intbidang masih dihitung untuk 4 byte, dan Int32Wrapperbidang dihitung untuk 8 byte
  • Menambahkan [StructLayout(LayoutKind.Sequential, Pack = 4)]ke setiap struct yang terlihat tidak mengubah hasil

Apakah ada yang punya penjelasan untuk ini (idealnya dengan dokumentasi referensi) atau saran tentang bagaimana saya bisa mendapatkan petunjuk ke CLR bahwa saya ingin field dikemas tanpa menentukan offset field yang konstan?

Jon Skeet
sumber
1
Anda tampaknya tidak benar-benar menggunakan Ref<T>tetapi stringmalah menggunakannya , bukan berarti itu membuat perbedaan.
tvanfosson
2
Apa yang terjadi jika Anda menempatkan dua membuat struct dengan dua TwoInt32Wrappers, atau Int64dan dan TwoInt32Wrappers? Bagaimana jika Anda membuat generik Pair<T1,T2> {public T1 f1; public T2 f2;}dan kemudian membuat Pair<string,Pair<int,int>>dan Pair<string,Pair<Int32Wrapper,Int32Wrapper>>? Kombinasi mana yang memaksa JITter untuk pad sesuatu?
supercat
7
@supercat: Ini mungkin yang terbaik bagi Anda untuk menyalin kode dan percobaan untuk diri sendiri - tapi Pair<string, TwoInt32Wrappers> tidak memberikan hanya 16 byte, sehingga akan mengatasi masalah ini. Menarik.
Jon Skeet
9
@SLaks: Terkadang saat struktur diteruskan ke kode native, Runtime akan menyalin semua data ke struktur dengan tata letak berbeda. Marshal.SizeOfakan mengembalikan ukuran struktur yang akan diteruskan ke kode asli, yang tidak perlu berhubungan dengan ukuran struktur dalam kode .NET.
supercat
5
Pengamatan menarik: Mono memberikan hasil yang benar. Lingkungan: CLR 4.0.30319.17020 di Unix 3.13.0.24 (64 bit) Int32Wrappers: 4 TwoInt32s: 8 TwoInt32Wrappers: 8 RefAndTwoInt32s: 16 RefAndTwoInt32Wrappers: 16
AndreyAkinshin

Jawaban:

85

Saya pikir ini adalah bug. Anda melihat efek samping dari tata letak otomatis, ia suka menyelaraskan bidang non-sepele ke alamat yang kelipatan 8 byte dalam mode 64-bit. Itu terjadi bahkan ketika Anda menerapkan [StructLayout(LayoutKind.Sequential)]atribut secara eksplisit . Itu tidak seharusnya terjadi.

Anda dapat melihatnya dengan membuat anggota struct publik dan menambahkan kode pengujian seperti ini:

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

Ketika breakpoint mencapai, gunakan Debug + Windows + Memory + Memory 1. Beralih ke integer 4-byte dan masukkan &testke kolom Address:

 0x000000E928B5DE98  0ed750e0 000000e9 11111111 00000000 22222222 00000000 

0xe90ed750e0adalah penunjuk string di mesin saya (bukan milik Anda). Anda dapat dengan mudah melihat Int32Wrappers, dengan tambahan 4 byte padding yang mengubah ukuran menjadi 24 byte. Kembali ke struct dan letakkan string terakhir. Ulangi dan Anda akan melihat penunjuk string masih pertama. Melanggar LayoutKind.Sequential, Anda punya LayoutKind.Auto.

Akan sulit untuk meyakinkan Microsoft untuk memperbaikinya, ini telah bekerja terlalu lama sehingga setiap perubahan akan merusak sesuatu . CLR hanya melakukan upaya untuk menghormati [StructLayout]versi terkelola dari sebuah struct dan membuatnya menjadi blittable, secara umum dengan cepat menyerah. Terkenal untuk setiap struct yang berisi DateTime. Anda hanya mendapatkan jaminan LayoutKind yang sebenarnya saat menyusun struct. Versi marshaled pasti adalah 16 byte, seperti yang Marshal.SizeOf()akan memberi tahu Anda.

Menggunakan LayoutKind.Explicitperbaikan itu, bukan apa yang ingin Anda dengar.

Hans Passant
sumber
7
"Akan sulit untuk meyakinkan Microsoft untuk memperbaiki ini, ini telah bekerja terlalu lama sehingga setiap perubahan akan merusak sesuatu." Fakta bahwa ini tampaknya tidak terwujud dalam 32 bit atau mono dapat membantu (sesuai komentar lainnya).
NPSF3000
Dokumentasi StructLayoutAttribute cukup menarik. Pada dasarnya, hanya jenis blittable yang dikontrol melalui StructLayout dalam memori terkelola. Menarik, tidak pernah tahu itu.
Michael Stum
@Soner tidak itu tidak memperbaikinya. Apakah Anda meletakkan Tata Letak di kedua bidang untuk offset 8? Jika demikian maka x dan y adalah sama dan mengubah yang satu mengubah yang lain. Jelas bukan apa yang diinginkan Jon.
BartoszAdamczewski
Mengganti stringdengan jenis referensi baru lainnya ( class) yang telah diterapkan [StructLayout(LayoutKind.Sequential)]tampaknya tidak mengubah apa pun. Di arah berlawanan, menerapkan [StructLayout(LayoutKind.Auto)]dengan struct Int32Wrappermengubah penggunaan memori di TwoInt32Wrappers.
Jeppe Stig Nielsen
1
"Akan sulit untuk meyakinkan Microsoft untuk memperbaiki ini, ini telah bekerja terlalu lama sehingga setiap perubahan akan merusak sesuatu." xkcd.com/1172
iCodeSometime
19

EDIT2

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

Kode ini akan selaras 8 byte sehingga struct akan memiliki 16 byte. Sebagai perbandingan ini:

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

Akan menjadi 4 byte sejajar sehingga struct ini juga akan memiliki 16 byte. Jadi alasannya di sini adalah bahwa struktur struktur di CLR ditentukan oleh jumlah bidang yang paling selaras, klas jelas tidak dapat melakukannya sehingga mereka akan tetap selaras 8 byte.

Sekarang jika kita menggabungkan semua itu dan membuat struct:

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

Ini akan memiliki 24 byte {x, y} akan memiliki 4 byte masing-masing dan {z, s} akan memiliki 8 byte. Setelah kami memperkenalkan tipe ref di struct CLR akan selalu menyelaraskan struct kustom kami agar sesuai dengan perataan kelas.

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

Kode ini akan memiliki 24 byte karena Int32Wrapper akan disejajarkan dengan panjang yang sama. Jadi, pembungkus struct kustom akan selalu sejajar dengan bidang paling tinggi / paling rata dalam struktur atau ke bidang paling signifikan internalnya sendiri. Jadi dalam kasus string ref yang sejajar 8 byte, pembungkus struct akan sejajar dengan itu.

Penutup bidang struct kustom di dalam struct akan selalu disejajarkan dengan bidang contoh rata tertinggi dalam struktur. Sekarang jika saya tidak yakin apakah ini bug tetapi tanpa beberapa bukti, saya akan tetap berpegang pada pendapat saya bahwa ini mungkin keputusan sadar.


EDIT

Ukuran sebenarnya akurat hanya jika dialokasikan di heap tetapi struct itu sendiri memiliki ukuran yang lebih kecil (ukuran persis bidangnya). Analisis lebih lanjut menunjukkan bahwa ini mungkin bug dalam kode CLR, tetapi perlu didukung oleh bukti.

Saya akan memeriksa kode cli dan memposting pembaruan lebih lanjut jika sesuatu yang berguna akan ditemukan.


Ini adalah strategi penyelarasan yang digunakan oleh pengalokasi mem .NET.

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

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

    Console.ReadKey();
}

Kode ini dikompilasi dengan .net40 di bawah x64, Di WinDbg mari lakukan hal berikut:

Mari kita temukan jenisnya di Heap terlebih dahulu:

    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

Setelah kita memilikinya, mari kita lihat apa yang ada di bawah alamat itu:

    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

Kami melihat bahwa ini adalah ValueType dan yang kami buat. Karena ini adalah array, kita perlu mendapatkan def ValueType dari satu elemen dalam array:

    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

Struktur sebenarnya 32 byte karena 16 byte dicadangkan untuk padding sehingga pada kenyataannya setiap struktur berukuran setidaknya 16 byte sejak awal.

jika Anda menambahkan 16 byte dari int dan string ref ke: 0000000003e72d18 + 8 byte EE / padding Anda akan berakhir di 0000000003e72d30 dan ini adalah titik pandang untuk referensi string, dan karena semua referensi adalah 8 byte empuk dari bidang data aktual pertama mereka ini membuat 32 byte kami untuk struktur ini.

Mari kita lihat apakah stringnya dilapisi seperti itu:

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  <<

Sekarang mari kita menganalisis program di atas dengan cara yang sama:

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

Sekarang struct kami adalah 48 byte.

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

Di sini situasinya sama, jika kita menambahkan ke 0000000003c22d18 + 8 byte string ref, kita akan berakhir di awal pembungkus Int pertama di mana nilainya sebenarnya menunjuk ke alamat tempat kita berada.

Sekarang kita dapat melihat bahwa setiap nilai adalah referensi objek lagi memungkinkan konfirmasi dengan mengintip 0000000003c22d20.

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

Sebenarnya itu benar karena ini adalah sebuah struct, alamat tidak memberi tahu kita apa pun jika ini adalah obj atau vt.

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

Jadi sebenarnya ini lebih seperti tipe Union yang akan mendapatkan 8 byte sejajar kali ini (semua paddings akan disejajarkan dengan struct induk). Jika tidak maka kita akan berakhir dengan 20 byte dan itu tidak optimal sehingga pengalokasi mem tidak akan pernah mengizinkannya terjadi. Jika Anda menghitungnya lagi, ternyata struct tersebut berukuran 40 byte.

Jadi jika Anda ingin lebih konservatif dengan memori, Anda tidak boleh mengemasnya dalam tipe struct kustom struct melainkan menggunakan array sederhana. Cara lain adalah dengan mengalokasikan memori dari heap (misalnya VirtualAllocEx) dengan cara ini Anda diberikan blok memori sendiri dan Anda mengelolanya sesuai keinginan.

Pertanyaan terakhir di sini adalah mengapa tiba-tiba kita bisa mendapatkan layout seperti itu. Nah, jika Anda membandingkan kode jited dan kinerja inkrementasi int [] dengan struct [] dengan inkrementasi bidang penghitung, yang kedua akan menghasilkan alamat selaras 8 byte menjadi satu kesatuan, tetapi ketika jited ini diterjemahkan menjadi kode assembly yang lebih dioptimalkan (singe LEA vs beberapa MOV). Namun dalam kasus yang dijelaskan di sini, kinerja sebenarnya akan lebih buruk jadi pendapat saya adalah bahwa ini konsisten dengan implementasi CLR yang mendasarinya karena ini adalah tipe khusus yang dapat memiliki banyak bidang sehingga mungkin lebih mudah / lebih baik untuk meletakkan alamat awal daripada a nilai (karena itu tidak mungkin) dan melakukan padding struct di sana, sehingga menghasilkan ukuran byte yang lebih besar.

BartoszAdamczewski
sumber
1
Melihat ini sendiri, ukurannya RefAndTwoInt32Wrappers bukan 32 byte - ini 24, yang sama dengan yang dilaporkan dengan kode saya. Jika Anda melihat di tampilan memori daripada menggunakan dumparray, dan melihat memori untuk larik dengan (katakanlah) 3 elemen dengan nilai yang dapat dibedakan, Anda dapat dengan jelas melihat bahwa setiap elemen terdiri dari referensi string 8-byte dan dua bilangan bulat 8-byte . Saya menduga itu dumparraymenunjukkan nilai sebagai referensi hanya karena tidak tahu cara menampilkan Int32Wrappernilai. "Referensi" itu menunjuk pada diri mereka sendiri; mereka bukanlah nilai yang terpisah.
Jon Skeet
1
Saya tidak begitu yakin dari mana Anda mendapatkan "16 byte padding", tetapi saya menduga itu mungkin karena Anda melihat ukuran objek array, yang akan menjadi "16 byte + count * ukuran elemen". Jadi array dengan hitungan 2 memiliki ukuran 72 (16 + 2 * 24), itulah yang dumparrayditampilkan.
Jon Skeet
@jon apakah Anda membuang banyak struct Anda dan memeriksa berapa banyak ruang yang ditempati di heap? Biasanya ukuran array disimpan di awal array, ini juga dapat diverifikasi.
BartoszAdamczewski
@jon ukuran yang dilaporkan berisi juga offset dari string yang dimulai pada 8. Saya tidak berpikir bahwa 8 byte tambahan yang disebutkan berasal dari array karena sebagian besar barang array berada sebelum alamat elemen pertama, tetapi saya akan memeriksa ulang dan mengomentari itu.
BartoszAdamczewski
1
Tidak, ThreeInt32Wrappers berakhir sebagai 12 byte, FourInt32Wrappers sebagai 16, FiveInt32Wrappers sebagai 20. Saya tidak melihat sesuatu yang logis tentang penambahan bidang jenis referensi yang mengubah tata letak secara drastis. Dan perhatikan bahwa cukup senang untuk mengabaikan penyelarasan 8-byte saat bidang memiliki tipe Int32. Saya tidak terlalu peduli tentang apa yang dilakukannya di tumpukan, jujur ​​- tetapi saya belum memeriksanya.
Jon Skeet
9

Ringkasan lihat jawaban @Hans Passant mungkin di atas. Layout Sequential tidak berfungsi


Beberapa pengujian:

Ini pasti hanya pada 64bit dan referensi objek "meracuni" struct tersebut. 32 bit melakukan apa yang Anda harapkan:

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

Segera setelah referensi objek ditambahkan, semua struct diperluas menjadi 8 byte, bukan ukuran 4 byte. Memperluas tes:

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

Seperti yang Anda lihat segera setelah referensi ditambahkan, setiap Int32Wrapper menjadi 8 byte, jadi bukan perataan sederhana. Saya mengecilkan alokasi array jika itu alokasi LoH yang selaras secara berbeda.

Ben Adams
sumber
4

Hanya untuk menambahkan beberapa data ke dalam campuran - Saya membuat satu jenis lagi dari yang Anda miliki:

struct RefAndTwoInt32Wrappers2
{
    string text;
    TwoInt32Wrappers z;
}

Program itu menulis:

RefAndTwoInt32Wrappers2: 16

Jadi sepertinya TwoInt32Wrappersstruct sejajar dengan benar di RefAndTwoInt32Wrappers2struct baru .

Jesse C. Slicer
sumber
Apakah Anda menjalankan 64 bit? Penjajaran baik-baik saja dalam 32 bit
Ben Adams
Temuan saya sama dengan orang lain untuk berbagai lingkungan.
Jesse C. Slicer