[英]Why the size of struct A is not equal size of struct B with same fields?
為什么struct A
的大小不等於struct B
大小?
而我需要做的是,它們的尺寸相同嗎?
using System;
namespace ConsoleApplication1
{
class Program
{
struct A
{
char a;
char c;
int b;
}
struct B
{
char a;
int b;
char c;
}
static void Main(string[] args)
{
unsafe
{
Console.WriteLine(sizeof(A));
Console.WriteLine(sizeof(B));
}
Console.ReadLine();
}
}
}
輸出是:
8
12
字段之間有一些填充。 使用先前字段和下一字段計算填充。
此外,這種情況應該是真的:
(size of struct) % (size of largest type) == 0
在您的情況下,最大的類型是int
,其大小是4
個字節。
struct A
{
char a; // size is 2, no previous field, next field size is 2 - no alignment needed
char c; // size is 2, previous size is 2 -> 2 + 2 = 4, next size is 4 - no alignment needed
int b; //size is 4, it is last field, size is 4 + 4 = 8.
//current size is 2 + 2 + 4 = 8
//8 % 4 == 0 - true - 8 is final size
}
struct B
{
char a; // size is 2, next size is 4, alignment needed - 2 -> 4, size of this field with alignment is 4
int b; // size is 4, previous is 4, next size is 2(lower) - no alignment needed
char c; // size is 2, previous is 4 + 4 = 8 - no alignment needed
//current size is 4 + 4 + 2 = 10
//but size should be size % 4 = 0 -> 10 % 4 == 0 - false, adjust to 12
}
如果你想要兩個結構相同的大小,你可以使用LayoutKind.Explicit
:
[StructLayout(LayoutKind.Explicit)]
public struct A
{
[FieldOffset(0)]
char a;
[FieldOffset(2)]
char c;
[FieldOffset(4)]
int b;
}
[StructLayout(LayoutKind.Explicit)]
public struct B
{
[FieldOffset(0)]
char a;
[FieldOffset(2)]
int b;
[FieldOffset(6)]
char c;
}
要么
您可以使用LayoutKind.Sequential
, Pack = 1
和CharSet = CharSet.Unicode
來獲取大小8。
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Unicode)]
public struct A
{
char a;
char c;
int b;
}
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Unicode)]
public struct B
{
char a;
int b;
char c;
}
此外,您可以獲得沒有unsafe
結構大小:
Console.WriteLine(System.Runtime.InteropServices.Marshal.SizeOf(typeof(A)));
Console.WriteLine(System.Runtime.InteropServices.Marshal.SizeOf(typeof(B)));
這是因為您的編譯器保留在struct
成員之間插入填充的權限,以及最后的一些空格。 (但是請注意,填充, 不是第一個成員之前允許的。)
這樣做是為了使成員的開始在易於尋址的內存位置上對齊。
特別是,編譯器可能在單個char
和int
之間插入填充。 因此,后跟int
的偶數個char
可能占用的空間少於char
后跟一個int
后跟奇數個char
的空間。
這是一個處理器實現細節,.NET非常難以隱藏。 變量需要一個存儲位置,允許處理器通過單個數據總線操作讀取和寫入值。 這使得變量地址的對齊非常重要。 讀取單個字節絕不是問題。 但是短(2個字節)的地址應該是2的倍數.int(4個字節)的地址應該是4的倍數。理想情況下,long或double(8個字節)的地址是a 8的倍數,但不能總是實現,而不是在32位處理器上。
與RISC內核不同,英特爾和AMD處理器允許不對齊的讀寫操作。 但這可能需要付出代價,可能需要兩個數據總線周期才能讀取兩個字節塊,一部分是字節的高位字節,另一部分是低位字節。 使用將這些字節混合到正確位置的電路。 這需要時間,通常需要額外的1到3個時鍾周期。 RISC內核上有很多時間來處理總線錯誤陷阱。
但更嚴重的是,它破壞了.NET內存模型。 它為簡單的值類型和對象引用提供了原子性保證。 未對齊的讀寫會破壞這一承諾。 它可能會導致撕裂 ,觀察正在寫入的部分字節。 更糟糕的是,它可以打破垃圾收集器。 GC主要依賴於原子更新的對象引用。
因此,當CLR確定結構或類的布局時,它必須確保滿足對齊要求。 如果不是那么它需要在變量之間留出額外的未使用空間。 並且最后可能有額外的空間來確保成員在存儲在數組中時仍然對齊。 額外空間的通用詞是“填充”。
具體到類聲明,它有[StructLayout(LayoutKind.Auto)],它可以隨機播放成員以實現最佳布局。 不是結構,默認情況下它們是LayoutKind.Sequential。 除了類和結構之外,靜態變量以及方法的參數和局部變量也需要這種對齊保證。 但幾乎不容易觀察到。
字段的順序是不同的; 我猜測大小是不同的,因為成員被填充(即,它們以一個偶數機器字開始的方式,以便以內存消耗為代價使訪問更容易)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.