2011-11-25 76 views
5

所有在C#中的默認struct都被視爲[StructLayout(LayoutKind.Sequential)] - 標記值類型。所以,讓我們的struct小號一些數量,並檢查該struct S的尺寸:CLR順序結構佈局:對齊和大小

using System; 
using System.Reflection; 
using System.Linq; 
using System.Runtime.InteropServices; 

class Foo 
{ 
    struct E { } 
    struct S0 { byte a; } 
    struct S1 { byte a; byte b; } 
    struct S2 { byte a; byte b; byte c; } 
    struct S3 { byte a; int b; } 
    struct S4 { int a; byte b; } 
    struct S5 { byte a; byte b; int c; } 
    struct S6 { byte a; int b; byte c; } 
    struct S7 { int a; byte b; int c; } 
    struct S8 { byte a; short b; int c; } 
    struct S9 { short a; byte b; int c; } 
    struct S10 { long a; byte b; } 
    struct S11 { byte a; long b; } 
    struct S12 { byte a; byte b; short c; short d; long e; } 
    struct S13 { E a; E b; } 
    struct S14 { E a; E b; int c; } 
    struct S15 { byte a; byte b; byte c; byte d; byte e; } 
    struct S16 { S15 b; byte c; } 
    struct S17 { long a; S15 b; } 
    struct S18 { long a; S15 b; S15 c; } 
    struct S19 { long a; S15 b; S15 c; E d; short e; } 
    struct S20 { long a; S15 b; S15 c; short d; E e; } 

    static void Main() 
    { 
    Console.WriteLine("name: contents => size\n"); 
    foreach (var type in typeof(Foo).GetNestedTypes(BindingFlags.NonPublic)) 
    { 
     var fields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance); 
     Console.WriteLine("{0}: {2} => {1}", type.Name, Marshal.SizeOf(type), 
     string.Join("+", fields.Select(_ => Marshal.SizeOf(_.FieldType)))); 
    } 
    } 
} 

輸出是(在x86/x64的相同):

name: contents => size 

E: => 1 
S0: 1 => 1 
S1: 1+1 => 2 
S2: 1+1+1 => 3 
S3: 1+4 => 8 
S4: 4+1 => 8 
S5: 1+1+4 => 8 
S6: 1+4+1 => 12 
S7: 4+1+4 => 12 
S8: 1+2+4 => 8 
S9: 2+1+4 => 8 
S10: 8+1 => 16 
S11: 1+8 => 16 
S12: 1+1+2+2+8 => 16 
S13: 1+1 => 2 
S14: 1+1+4 => 8 
S15: 1+1+1+1+1 => 5 
S16: 5+1 => 6 
S17: 8+5 => 16 
S18: 8+5+5 => 24 
S19: 8+5+5+1+2 => 24 
S20: 8+5+5+2+1 => 24 

望着這導致我無法理解規則集CLR用於順序結構的佈局(字段對齊和總大小)。有人可以解釋我這種行爲嗎?

+3

您是否要求CLR用於託管空間中的結構(這是一個實現細節)的規則集,或者在託管非託管空間之間編組結構時由編組人員使​​用的規則集(Marshal.SizeOf返回結構體的大小在編組後,而不是管理空間中的結構)? – dtb

+0

將其縮小一點,哪些特定結果出乎意料? –

+0

@dtb順序佈局這兩件事情是完全一樣的。 Marshal.SizeOf()提供完全相同的大小,因爲C#的sizeof運算符返回。 – ControlFlow

回答

11

所有的字段根據其類型對齊。本機類型(int,byte等)都按其大小排列。例如,一個int將始終是4個字節的倍數,而一個字節可以在任何地方。

如果較小的字段出現在int之前,則會根據需要添加填充以確保int正確對齊到4個字節。這就是爲什麼S5(1 + 1 + 4 = 8)和S8(1 + 2 + 4 = 8)將具有填充,並最終將相同的大小:

[1][1][ ][ ][4] // S5 
[1][ ][ 2 ][4] // S8 

另外,結構本身繼承的對準其最對齊的字段(即對於S5S8,int是最對齊的字段,因此它們都具有4的對齊)。對齊是這樣繼承的,所以當你有一個結構數組時,所有結構中的所有字段都將被正確對齊。所以,4 + 2 = 8

[4][2][ ][ ] // starts at 0 
[4][2][ ][ ] // starts at 8 
[4][2][ ][ ] // starts at 16 

通知的4總是由4.在不從最對齊的字段繼承對齊,在陣列中每隔一個元件將具有其int由6個字節,而不是4對準:

[4][2] // starts at 0 
[4][2] // starts at 6 -- the [4] is not properly aligned! 
[4][2] // starts at 12 

因爲不就是所有的架構允許未對齊的內存地址讀取,就連那些有這將是非常糟糕的(可能是相當大的,如果在高速緩存行或頁邊界)的性能罰款,這樣做它。

除了基本的性能,對齊也會發揮併發作用。 C#內存模型保證讀取/寫入最多4個字節寬的本機類型是原子性的,並且.NET具有類似於Interlocked類的原子特性。像這些原子操作歸結爲CPU指令本身需要對齊內存訪問工作。

正確對齊非常重要!

您經常會看到聰明的本機編碼人員在佈置其結構時將所有這些記在腦海中,將所有字段從大到小排序,以儘量保持填充,從而將結構大小降至最低。

+0

比你好!但是非本地類型的字段呢? – ControlFlow

+0

如果結構繼承最對齊的字段的對齊,爲什麼struct S22 {S16 a; S15 b; }'產生'6 + 5 => 11'輸出?最對齊的字段是大小爲「6」的「a」,不是嗎? – ControlFlow

+0

非本地類型繼承其最對齊字段的對齊方式。所以'S15'與'1'對齊,因爲它的最對齊的字段是'byte'類型。 'S16'也與'1'對齊,因爲它只包含一個'S15'和'byte'。 –