2011-11-25 10 views
5

Tutti gli struct s in C# sono considerati come valori di tipo con segno [StructLayout(LayoutKind.Sequential)]. Quindi, consente di prendere un po 'numero di struct s e controllare le dimensioni di questo struct s:Layout sequenziale delle strutture CLR: allineamento e dimensioni

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

uscita è (lo stesso su 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 

Guardando questo si traduce non riesco a capire il layout (allineamento dei campi e dimensione totale) set di regole CLR utilizzato per le strutture sequenziali. Qualcuno può spiegarmi questo comportamento?

+3

Si sta richiedendo il set di regole utilizzato dal CLR per le strutture nello spazio gestito (che è un dettaglio di implementazione) o il set di regole utilizzato dal gestore di marshalling durante il marshalling delle strutture tra uno spazio gestito non gestito (Marshal.SizeOf restituisce la dimensione di una struttura dopo il marshalling, non della struttura nello spazio gestito)? – dtb

+0

Riducetelo un po ', quali risultati particolari sono inaspettati? –

+0

@dtb con layout sequenziale queste due cose sono completamente uguali. 'Marshal.SizeOf()' fornisce completamente le stesse dimensioni, come restituisce l'operatore C# 'sizeof'. – ControlFlow

risposta

11

Tutti i campi sono allineati in base al tipo. I tipi nativi (int, byte, ecc.) Sono tutti allineati in base alla loro dimensione. Ad esempio, un int sarà sempre a un multiplo di 4 byte, mentre un byte può essere ovunque.

Se i campi più piccoli precedono lo int, il riempimento verrà aggiunto se necessario per garantire che lo int sia correttamente allineato a 4 byte. Questo è il motivo S5 (1 + 1 + 4 = 8) e S8 (1 + 2 + 4 = 8) avrà imbottitura e finire la stessa dimensione:

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

Inoltre, la struct stessa eredita l'allineamento il suo campo più allineato (per esempio per S5 e S8, int è il campo più allineato, quindi entrambi hanno un allineamento di 4). L'allineamento è ereditato in questo modo in modo che quando si dispone di una matrice di strutture, tutti i campi in tutte le strutture siano allineati correttamente. Quindi, 4 + 2 = 8.

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

comunicazione 4 è sempre allineato per 4. senza ereditare dal campo più allineato, ogni altro elemento in un array avrebbe relativo int allineato di 6 byte invece di 4 :

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

Questo sarebbe molto male perché non tutte le architetture consentire la lettura da indirizzi di memoria non allineati, e anche quelli che hanno un (potenzialmente abbastanza grande, se su una linea di cache o limite di pagina) penalizzazione delle prestazioni per fare esso.

Oltre alle prestazioni di base, anche l'allineamento entra in gioco con la concorrenza. Il modello di memoria C# garantisce letture/scritture dei tipi nativi fino a 4 byte di larghezza sono atomici, e .NET ha caratteristiche atomiche come la classe Interlocked. Le operazioni atomiche come queste si riducono alle istruzioni della CPU che richiedono l'accesso alla memoria allineato al lavoro.

L'allineamento corretto è molto importante!

Spesso si vedono intelligenti programmatori nativi che tengono tutto questo a mente mentre dispongono le proprie strutture, ordinando tutti i campi dal più grande al più piccolo, nel tentativo di mantenere il riempimento, e quindi la dimensione della struttura, al minimo.

+0

Grazie! Ma per quanto riguarda i campi di tipi non nativi? – ControlFlow

+0

Se struct eredita l'allineamento dal campo più allineato, perché 'struct S22 {S16 a; S15 b; } 'produce l'output' 6 + 5 => 11'? il campo più allineato è 'a' di dimensione' 6', non è vero? I tipi non nativi – ControlFlow

+0

ereditano l'allineamento del loro campo più allineato. Quindi 'S15' è allineato da' 1', perché il suo campo più allineato è di tipo 'byte'. Anche 'S16' è allineato da' 1', perché contiene solo 'S15' e' byte'. –

Problemi correlati