2011-11-25 8 views
5

Todos struct s en C# por defecto son tratados como [StructLayout(LayoutKind.Sequential)] tipos de valor -marcado. Así que vamos a tomar algún número de struct s e inspeccionar tamaños de esta struct s:CLR estructuras secuenciales diseño: alineación y tamaño

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

de salida es la misma (en 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 

mirando este resultado no puedo entender el conjunto de reglas de diseño (alineación de campos y tamaño total) CLR utilizado para las estructuras secuenciales. ¿Alguien puede explicarme este comportamiento?

+3

¿Está pidiendo el conjunto de reglas utilizado por el CLR para estructuras en el espacio gestionado (que es un detalle de implementación) o el conjunto de reglas utilizado por el marcador cuando coordina estructuras entre un espacio gestionado y no gestionado (Marshal.SizeOf devuelve el tamaño de una estructura después de ordenar, no de la estructura en el espacio administrado)? – dtb

+0

Reduzca un poco, ¿qué resultados particulares son inesperados? –

+0

@dtb con diseño secuencial, estas dos cosas son completamente iguales. 'Marshal.SizeOf()' da completamente los mismos tamaños, como vuelve el operador C# 'sizeof'. – ControlFlow

Respuesta

11

Todos los campos están alineados en función de su tipo. Los tipos nativos (int, byte, etc.) están todos alineados por su tamaño. Por ejemplo, un int siempre estará en un múltiplo de 4 bytes, mientras que un byte puede estar en cualquier lugar.

Si los campos más pequeños vienen antes de int, se agregará relleno si es necesario para garantizar que el int esté alineado correctamente con 4 bytes. Esta es la razón por S5 (1 + 1 + 4 = 8) y S8 (1 + 2 + 4 = 8) tendrá el relleno y terminar el mismo tamaño:

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

Además, la estructura en sí hereda la alineación de su campo más alineado (es decir. para S5 y S8, int es el campo más alineado, por lo tanto de ellos tienen una alineación de 4). La alineación se hereda así para que cuando tenga una matriz de estructuras, todos los campos en todas las estructuras estén alineados correctamente. Por lo tanto, 4 + 2 = 8.

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

Aviso el 4 siempre está alineado por 4. Sin heredando del campo más alineado con, cada otro elemento de una matriz tendría su int alineado por 6 bytes en lugar de 4 :

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

Esto sería muy malo porque no todas las arquitecturas de permitir la lectura de las direcciones de memoria no alineados, e incluso los que lo hacen tienen un (potencialmente muy grande, si en una línea de caché o límite de página) penalización de rendimiento para hacer eso.

Más allá del rendimiento básico, la alineación también entra en juego con la concurrencia. El modelo de memoria C# garantiza que las lecturas/escrituras de los tipos nativos de hasta 4 bytes de ancho son atómicas, y .NET tiene funciones atómicas como la clase Interlocked. Las operaciones atómicas como estas se reducen a las instrucciones de la CPU que requieren el acceso alineado a la memoria para funcionar.

¡La alineación correcta es muy importante!

A menudo verá inteligentes codificadores nativos tener todo esto en cuenta al diseñar sus estructuras, ordenando todos los campos de mayor a menor en un esfuerzo para mantener el relleno, y por lo tanto el tamaño de la estructura, a un mínimo.

+0

¡Gracias! Pero, ¿qué pasa con los campos de tipos no nativos? – ControlFlow

+0

Si struct hereda la alineación del campo más alineado, ¿por qué 'struct S22 {S16 a; S15 b; } 'produce' 6 + 5 => 11' salida? el campo más alineado es 'a' de tamaño' 6', ¿no es así? – ControlFlow

+0

tipos no nativos heredan la alineación de su campo más alineado. Así que 'S15' está alineado por' 1', porque su campo más alineado es de tipo 'byte'. 'S16' también está alineado por' 1', porque solo contiene un 'S15' y' byte'. –

Cuestiones relacionadas