¿Cómo controlo el número de bytes consumidos por una estructura?

Si estoy creando una estructura relativamente grande, ¿cómo puedo calcular los bytes que ocupa en la memoria?

Podemos hacerlo manualmente, pero si la estructura es lo suficientemente grande, ¿cómo lo hacemos? ¿Hay algún fragmento de código o aplicación?

Puede usar el operador sizeof o la función SizeOf .
Hay algunas diferencias entre esas opciones, vea los enlaces de referencia para más.

De todos modos, una buena forma de usar esa función es tener un método genérico o un método de extensión como estos:

 static class Test { static void Main() { //This will return the memory usage size for type Int32: int size = SizeOf(); //This will return the memory usage size of the variable 'size': //Both lines are basically equal, the first one makes use of ex. methods size = size.GetSize(); size = GetSize(size); } public static int SizeOf() { return System.Runtime.InteropServices.Marshal.SizeOf(typeof(T)); } public static int GetSize(this object obj) { return System.Runtime.InteropServices.Marshal.SizeOf(obj); } } 

Las estructuras han sido bestias problemáticas en ingeniería informática durante mucho tiempo. Su diseño de memoria depende mucho del hardware. Para que sean eficientes, sus miembros deben estar alineados para que la CPU pueda leer y escribir sus valores rápidamente sin tener que multiplexar los bytes para que se ajusten al ancho del bus de memoria. Cada comstackdor tiene su propia estrategia de empaquetar los miembros, a menudo dirigida por, por ejemplo, la directiva #pragma pack en un progtwig C o C ++.

Lo cual está bien, pero es un problema en los escenarios de interoperabilidad. Donde un trozo de código puede hacer suposiciones diferentes sobre el diseño de la estructura que otro trozo, comstackdo por un comstackdor diferente. Puedes ver esto de nuevo en COM, la solución de abuelo de .NET para la progtwigción de interoperabilidad. COM tiene muy poco soporte para el manejo de estructuras. No los admite como un tipo de automatización nativo, pero tiene una solución alternativa a través de la interfaz IRecordInfo. Lo que permite que un progtwig descubra el diseño de la memoria en tiempo de ejecución a través de una statement explícita de la estructura en una biblioteca de tipos. Lo cual funciona bien, pero es bastante ineficiente.

Los diseñadores de .NET tomaron una decisión muy valiente y correcta para resolver este problema. Hicieron el diseño de la memoria de una estructura completamente indescifrable. No hay forma documentada de recuperar el desplazamiento de un miembro. Y, por extensión, no hay forma de descubrir el tamaño de la estructura. La respuesta favorita de todos, use Marshal.SizeOf () no es la solución. Eso devuelve el tamaño de la estructura después de calcularla , el tamaño que necesitarías pasar a, por ejemplo, Marshal.AllocCoTaskMem () antes de llamar a Marshal.StructureToPtr. Eso arregla y alinea los miembros de la estructura de acuerdo con el atributo [StructLayout] que está asociado con la estructura. Tenga en cuenta que este atributo no es necesario para las estructuras (como lo es para las clases), el tiempo de ejecución implementa una predeterminada que utiliza el orden declarado para los miembros.

Un efecto secundario muy bueno de que el diseño no se puede descubrir es que el CLR puede jugar trucos con él. Al empaquetar los miembros de la estructura y alinearlos, la disposición puede obtener agujeros que no almacenan ningún dato. Llamado bytes de relleno. Dado que el diseño es indescifrable, el CLR puede usar el relleno. Mueve un miembro si es lo suficientemente pequeño como para caber en ese agujero. Ahora obtendrá una estructura cuyo tamaño es más pequeño de lo que normalmente se requeriría dado el diseño de la estructura declarada. Y, en particular, Marshal.SizeOf () devolverá el valor incorrecto para el tamaño de la estructura, devuelve un valor que es demasiado grande.

Para resumir, no hay una forma general de obtener un valor preciso para el tamaño de la estructura mediante progtwigción. Lo mejor es no hacer la pregunta. Marshal.SizeOf () te dará una estimación aproximada, suponiendo que la estructura es blittable. Si necesita un valor preciso por algún motivo, entonces podría ver el código máquina generado de un método que declara una variable local del tipo de estructura y compararla con el mismo método sin esa variable local. Verá la diferencia en el ajuste del puntero de stack, la instrucción “sub esp, xxx” en la parte superior del método. Por supuesto, dependerá de la architecture, normalmente obtendrá una estructura más grande en el modo de 64 bits.

Puede usar la palabra clave sizeof() para estructuras definidas por el usuario que no contengan ningún campo o propiedades que sean tipos de referencia, o use Marshal.SizeOf(Type) o Marshal.SizeOf(object) para obtener el tamaño no administrado de un tipo o una estructura que tiene un diseño secuencial o explícito.

Escribí una pequeña biblioteca pequeña en CIL (lenguaje ensamblador de .NET ) para exponer algunas funcionalidades ordenadas que no están disponibles en C #. sizeof el sizeof instrucción.

Difiere significativamente del operador de sizeof en C #. Básicamente, obtiene el tamaño de una estructura (o tipo de referencia, que actúa de forma divertida con algunas optimizaciones), incluido el relleno y todo. Entonces, si creara una matriz de T , entonces puede usar sizeof para determinar la distancia entre cada elemento de la matriz en bytes . También es un código completamente verificable y administrado. Tenga en cuenta que en Mono había un error (¿pre-3.0?) Que causaría que el tamaño de los tipos de referencia se informara incorrectamente, lo que se expandiría a las estructuras que contienen tipos de referencia.

De todos modos, puede descargar la biblioteca con licencia de BSD (y CIL) de BitBucket . También puedes ver algunos códigos de ejemplo y algunos detalles más en mi blog .

Desea utilizar System.Runtime.InteropServices.Marshal.SizeOf () :

 struct s { public Int64 i; } public static void Main() { s s1; s1.i = 10; var s = System.Runtime.InteropServices.Marshal.SizeOf(s1); } 

En .NET Core , el sizeof instrucción CIL ha sido expuesto a través de la clase Unsafe recientemente agregada. Agregue una referencia al paquete System.Runtime.CompilerServices.Unsafe y simplemente haga esto:

 int size = Unsafe.SizeOf(); 

Funciona también para tipos de referencia (devolverá 4 u 8 dependiendo de la architecture de su computadora).

Puede usar System.Runtime.InteropServices.Marshal.SizeOf() para obtener el tamaño en bytes también.

0. Para el código de ejemplo:

 using System; using System.Diagnostics; using System.Reflection.Emit; using System.Runtime.InteropServices; 

1. Estructura de demostración

 [Serializable, StructLayout(LayoutKind.Sequential, Pack = 128)] public struct T { public int a; public byte b; public int c; public String d; public short e; }; 

2. Restar punteros administrados:

 /// Return byte-offset between managed addresses of struct instances 'hi' and 'lo' public static long IL.RefOffs(ref T1 hi, ref T2 lo) { ... } 
 public static class IL { public delegate long _ref_offs(ref T1 hi, ref T2 lo); public static readonly _ref_offs RefOffs; static IL() { var dm = new DynamicMethod( Guid.NewGuid().ToString(), typeof(long), new[] { typeof(T1).MakeByRefType(), typeof(T2).MakeByRefType() }, typeof(Object), true); var il = dm.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Sub); il.Emit(OpCodes.Conv_I8); il.Emit(OpCodes.Ret); RefOffs = (_ref_offs)dm.CreateDelegate(typeof(_ref_offs)); } }; 

3. Revelar el diseño de la estructura interna administrada:

 static class demonstration { /// Helper thunk that enables automatic type-inference from argument types static long RefOffs(ref T1 hi, ref T2 lo) => IL.RefOffs(ref hi, ref lo); public static void Test() { var t = default(T); var rgt = new T[2]; Debug.Print("Marshal.Sizeof: {0,2}", Marshal.SizeOf()); Debug.Print("&rgt[1] - &rgt[0]: {0,2}", RefOffs(ref rgt[1], ref rgt[0])); Debug.Print("int &t.a {0,2}", RefOffs(ref ta, ref t)); Debug.Print("byte &t.b {0,2}", RefOffs(ref tb, ref t)); Debug.Print("int &t.c {0,2}", RefOffs(ref tc, ref t)); Debug.Print("String &t.d {0,2}", RefOffs(ref td, ref t)); Debug.Print("short &t.e {0,2}", RefOffs(ref te, ref t)); } }; 

4. Resultados y discusión

La configuración StructLayout(..., Pack) se puede agregar a la statement de struct T con cualquiera de los siguientes valores: {0, 1, 2, 4, 8, 16, 32, 64, 128} . El valor predeterminado cuando Pack no se especifica, o equivalentemente con Pack=0 -sets packing igual a IntPtr.Size ( 4 en x86, 8 en x64).

Los resultados de ejecutar el progtwig anterior muestran que el valor del Pack solo afecta el tamaño de Marshal.SizeOf reportado por Marshal.SizeOf , y no el tamaño real de una sola imagen de memoria T , que se supone que es el byte-offset entre instancias físicamente adyacentes. El código de prueba mide esto a través de una matriz administrada de diagnóstico new T[2] asignada a rgt .

========= x86 ========== ========= x64 ==========

-------- Pack=1 -------- -------- Pack=1 --------
Marshal.Sizeof(): 15 Marshal.Sizeof(): 19
&rgt[1] - &rgt[0]: 16 &rgt[1] - &rgt[0]: 24

-------- Pack=2 -------- -------- Pack=2 --------
Marshal.Sizeof(): 16 Marshal.Sizeof(): 20
&rgt[1] - &rgt[0]: 16 &rgt[1] - &rgt[0]: 24

--- Pack=4/0/default --- -------- Pack=4 --------
Marshal.Sizeof(): 20 Marshal.Sizeof(): 24
&rgt[1] - &rgt[0]: 16 &rgt[1] - &rgt[0]: 24

-------- Pack=8 -------- --- Pack=8/0/default ---
Marshal.Sizeof(): 20 Marshal.Sizeof(): 32
&rgt[1] - &rgt[0]: 16 &rgt[1] - &rgt[0]: 24

-- Pack=16/32/64/128 --- -- Pack=16/32/64/128 ---
Marshal.Sizeof(): 20 Marshal.Sizeof(): 32
&rgt[1] - &rgt[0]: 16 &rgt[1] - &rgt[0]: 24

Como se señaló, encontramos que por architecture ( x86 , x64 ), el diseño del campo administrado es consistente independientemente de la configuración del Pack . Aquí están los offsets de campo administrados reales, una vez más para el modo de 32 y 64 bits, según lo informado por el código anterior:

┌─offs─┐
field type size x86 x64
===== ====== ==== === ===
a int 4 4 8
b byte 1 14 18
c int 4 8 12
d String 4/8 0 0
e short 2 12 16

Lo más importante que hay que notar en esta tabla es que, (como menciona Hans ), las compensaciones de campo informadas no son monótonas con respecto a su orden de statement. Los campos de ValueType instancias de ValueType siempre se reordenan para que todos los campos de tipo de referencia vayan primero. Podemos ver que el campo String d está en offset 0.

Reordenación adicional optimiza el orden de campo para compartir el exceso de relleno interno que de otro modo se desperdiciaría. Podemos ver esto con el campo byte b , que ha pasado de ser el segundo campo declarado a ser el último.

Naturalmente, al ordenar las filas de la tabla anterior, podemos revelar el verdadero diseño administrado interno de .NET ValueType . Tenga en cuenta que podemos obtener este diseño a pesar del ejemplo de estructura T contiene una referencia gestionada ( String d ) y, por lo tanto, no es blittable :

============= x86 ============ ============= x64 ============
field type size offs end field type size offs end
===== ====== ==== ==== === ===== ====== ==== ==== ===
d String 4 0 … 4 d String 8 0 … 8
a int 4 4 … 8 a int 4 8 … 12
c int 4 8 … 12 c int 4 12 … 16
e short 2 12 … 14 e short 2 16 … 18
b byte 1 14 … 15 b byte 1 18 … 19

internal padding: 1 15 … 16 internal padding: 5 19 … 24

x86 managed total size: 16 x64 managed total size: 24

Anteriormente, determinamos el tamaño de una sola instancia de estructura gestionada calculando la diferencia de desplazamiento de bytes entre instancias adyacentes. Teniendo esto en cuenta, las últimas líneas de la tabla anterior muestran trivialmente el relleno que el CLR aplica internamente al final de la estructura de ejemplo T Recuerde de nuevo, por supuesto, que este relleno interno está fijado por el CLR y está completamente fuera de nuestro control.

5. Coda
Para completar, esta última tabla muestra la cantidad de relleno que se sintetizará sobre la marcha durante la clasificación . Tenga en cuenta que, en algunos casos, este relleno de Marshal es negativo en comparación con el tamaño administrado interno. Por ejemplo, a pesar de que el tamaño administrado interno de una T en x64 es de 24 bytes, la estructura emitida por cálculo de referencias puede ser de 19 o 20 bytes con Pack=1 o Pack=2 , respectivamente.

pack size offs end pack size offs end
============= ==== ==== === ============= ==== ==== ===
1 0 15 … 15 1 0 19 … 19
2 1 15 … 16 2 1 19 … 20
4/8/16/32/64… 5 15 … 20 4/8/16/32/64… 5 19 … 24