C#-struct實例字段的內存佈局(Layout)和大小(Size)

struct實例字段的內存佈局(Layout)和大小(Size)


        在C/C++中,struct類型中的成員的一旦聲明,則實例中成員在內存中的佈局(Layout)順序就定下來了,即與成員聲明的順序相同,並且在默認情況下總是按照結構中佔用空間最大的成員進行對齊(Align);當然我們也可以通過設置或編碼來設置內存對齊的方式.
        然而在.net託管環境中,CLR提供了更自由的方式來控制struct中Layout:我們可以在定義struct時,在struct上運用StructLayoutAttribute特性來控制成員的內存佈局。默認情況下,struct實例中的字段在棧上的佈局(Layout)順序與聲明中的順序相同,即在struct上運用[StructLayoutAttribute(LayoutKind.Sequential)]特性,這樣做的原因是結構常用於和非託管代碼交互的情形。如果我們正在創建一個與非託管代碼沒有任何互操作的struct類型,我們很可能希望改變C#編譯器的這種默認規則,因此LayoutKind除了Sequential成員之外,還有兩個成員AutoExplicit,給StructLayoutAttribute傳入LayoutKind.Auto可以讓CLR按照自己選擇的最優方式來排列實例中的字段;傳入LayoutKind.Explicit可以使字段按照我們的在字段上設定的FieldOffset來更靈活的設置字段排序方式,但這種方式也挺危險的,如果設置錯誤後果將會比較嚴重。下面就看幾個示例,算下四個struct各佔多少Byte?

1.[StructLayout(LayoutKind.Sequential)]

struct StructDeft//C#編譯器會自動在上面運用[StructLayout(LayoutKind.Sequential)]
{
    
bool i;  //1Byte

    double c;//8byte
    bool b;  //1byte
}
    

        sizeof(StructDeft)得到的結果是24byte!啊哈,本身只有10byte的數據卻佔有了24byte的內存,這是因爲默認(LayoutKind.Sequential)情況下,CLR對struct的Layout的處理方法與C/C++中默認的處理方式相同(8+8+8=24),即按照結構中佔用空間最大的成員進行對齊(Align)。10byte的數據卻佔有了24byte,嚴重地浪費了內存,所以如果我們正在創建一個與非託管代碼沒有任何互操作的struct類型,最好還是不要使用默認的StructLayoutAttribute(LayoutKind.Sequential)特性 

2.[StructLayout(LayoutKind.Explicit)]

[StructLayout(LayoutKind.Explicit)]
struct
 BadStruct
{
    [FieldOffset(
0
)]
    
public bool i;  //1Byte

    [FieldOffset(0)]
    
public double c;//8byte

    [FieldOffset(0)]
    
public bool b;  //1byte

}

        sizeof(BadStruct)得到的結果是9byte,顯然得出的基數9顯示CLR並沒對結構體進行任何內存對齊(Align);本身要佔有10byte的數據卻只佔了9byte,顯然有些數據被丟失了,這也正是我給struct取BadStruct作爲名字的原因。如果在struct上運用了[StructLayout(LayoutKind.Explicit)],計算FieldOffset一定要小心,例如我們使用上面BadStruct來進行下面的測試:

StructExpt e = new StructExpt();
e.c 
= 0
;
e.i 
= true
;
Console.WriteLine(e.c);

        輸出的結果不再是0了,而是4.94065645841247E-324,這是因爲e.c和e.i共享同一個byte,執行“e.i = true;時”也改變了e.c,CPU在按照浮點數的格式解析e.c時就得到了這個結果.所以在運用LayoutKind.Explicit時千萬別吧FieldOffset算錯了:) 

3.[StructLayout(LayoutKind.Auto)]
        sizeof(StructAuto)得到的結果是12byte。下面來測試下這StructAuto的三個字段是如何擺放的:

unsafe
{
      StructAuto s 
= new
 StructAuto();
      Console.WriteLine(
string.Format("i:{0}", (int)&
(s.i)));
      Console.WriteLine(
string.Format("c:{0}", (int)&
(s.c)));
      Console.WriteLine(
string.Format("b:{0}", (int)&
(s.b)));
}

// 測試結果:
i:1242180
c:
1242172
b:
1242181

        即CLR會對結構體中的字段順序進行調整,將i調到c之後,使得StructAuto的實例s佔有儘可能少的內存,並進行4byte的內存對齊(Align),字段順序調整結果如下圖所示:


4.空struct實例的Size

struct EmptyStruct{}

    無論運用上面LayoutKind的Explicit、Auto還是Sequential,得到的sizeof(EmptyStct)都是1byte。 

結論:
        默認(LayoutKind.Sequential)情況下,CLR對struct的Layout的處理方法與C/C++中默認的處理方式相同,即按照結構中佔用空間最大的成員進行對齊(Align)
        使用LayoutKind.Explicit的情況下,CLR不對結構體進行任何內存對齊(Align),而且我們要小心就是FieldOffset
        使用LayoutKind.Auto的情況下,CLR會對結構體中的字段順序進行調整,使實例佔有儘可能少的內存,並進行4byte的內存對齊(Align)。 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章