Siguiendo con el el artículo anterior de la memoria en .NET donde explicaba como esta estructurada, sigo con las variables, que en .NET principalmente son de dos tipos:
- Tipos por valor:
Tipo (alias) | Bytes | Rango |
Char (char) | 2 | Caracteres |
Bolean (bool) | 4 | True ó False |
IntPtr | ? | Puntero nativo |
DateTime (date) | 8 | 1/1/0001 12:00:00 AM a 12/31/9999 11:59:59 PM |
SByte (sbyte) | 1 | -128 a 127 |
Byte (byte) | 1 | 0 a 255 |
Int16 (short) | 2 | -32768 a 32767 |
Int32 (int) | 4 | -2147483648 a 2147483647 |
UInt32 (uint) | 4 | 0 a 4294967295 |
Int64 (long) | 8 | -9223372036854775808 a 9223372036854775807 |
Single (float) | 4 | -3.402823E+38 a 3.402823E+38 |
Double (double) | 8 | -1.79769313486232E+308 a 1.79769313486232E+308 |
Decimal (decimal) | 16 | -79228162514264337593543950335 a 79228162514264337593543950335 |
- Se conoce su tamaño antes de su inicialización, el más grande tiene 16 Bytes.
- Heredan de System.ValueType y ninguno puede ser extendido (sealed)(ni siquiera ValueType).
- Se almacenan en el thread stack cuando son variables locales y expresiones intermedias, y en el managed heap en el resto de situaciones (variables globales ó/y estáticas).
- Las estructuras (struct) y enumeraciones (enum) son tipos por valor también y responden al mismo comportamiento.
- La memoria ocupada por estos tipos se elimina en cuanto están fuera de ámbito en caso de estar en el thread stack, y por medio del GC cuando estan en el managed heap (desapareciendo junto con la clase a la que está asociada).
- Tipos por referencia (objeto):
- No se conoce su tamaño hasta después de su inicialización.
- Todos los demás tipos de la BCL.
- Heredan directamente de System.Object, y pueden ser extendidos a no ser que se especifique lo contrario (sealed).
- Se almacenan siempre en el managed heap, y son accedidos desde el thread stack mediante una referencia.
- Cuando declaramos una variable de tipo objeto, se crea una referencia en el thread stack, y al crear la instancia en el managed heap por medio del operador new, su dirección en memoria se asigna a la susodicha variable. De esta forma se puede acceder al objeto por medio de la indirección de la referencia.
- La memoria ocupada por estos tipos la libera el GarbageCollector de forma no determinística.
Si, no me he equivocado, los tipos por valor no se almacenan siempre en el thread stack :D, como decía solo se almacenan ahí cuando son variables locales y expresiones intermedias.
Como decía en el artículo anterior, el thread stack es un espacio de almacenamiento exclusivo de su hilo de ejecución y no puede ser compartido, sin embargo… te pones a pensar y te das cuenta de que puedes compartir variables de tipo por valor entre threads… por lo que lo primero que te viene a la mente es que este realizando boxing/unboxing para ello… pero si precisamente una de las metas del performance es evitar esas dos operaciones … no tiene sentido!
Menos mal que tiene uno el .NET Reflector siempre a mano para salir de la penumbra xD
Vamos a ver como se comporta el CLR con una variable local y una variable externa de otra clase, ambas de tipo por valor:
class Program
{
static void Main(string[] args)
{
MyClass exampleClass = new MyClass();
exampleClass.globalInt = 6;
Int32 localInt = 5;
Console.WriteLine(localInt);
Console.WriteLine(exampleClass.globalInt);
}
}
class MyClass
{
public Int32 globalInt;
}
localInt es un Int32 declarado localmente y debería estar en el thread stack, y MyClass.globalInt es un Int32 declarado como miembro de un tipo por referencia, por lo que debería estar en el managed heap… vamos a verlo. El método Main genera el siguiente código CIL (obviadas las instrucciones que no interesan ahora):
1: .locals init (
2: [0] class Test.MyClass exampleClass,
3: [1] int32 localInt)
4: L_0000: newobj instance void Test.MyClass::.ctor()
5: L_0005: stloc.0
6: L_0006: ldloc.0
7: L_0007: ldc.i4.6
8: L_0008: stfld int32 Test.MyClass::globalInt
9: L_000d: ldc.i4.5
10: L_000e: stloc.1
11: L_000f: ldloc.1
12: L_0010: call void [mscorlib]System.Console::WriteLine(int32)
13: L_0015: ldloc.0
14: L_0016: ldfld int32 Test.MyClass::globalInt
15: L_001b: call void [mscorlib]System.Console::WriteLine(int32)
16: L_0020: ret
Lo primero que uno mira es si aparecen las instruciones box/unbox para realizar boxing/unboxing, pero como vemos no aparecen, por lo cual, se esta accediendo y asignando a una variable de tipo por valor en el heap sin realizar ninguna de estas dos operaciones.
Voy a explicarlo brevemente:
- Asignación del miembro de MyClass, líneas 7 y 8: carga en valor ‘6’ en pila y lo asigna mediante la instrucción stfld, que precisamente sirve para asignar valores a una variable contenida en un objeto.
- Asignación de la variable local localInt, líneas 9 y 10: carga el valor ‘5’ en pila y lo asigna mediante la instrución stloc a la variable local en el puesto 1 (la 0 es Test.MyClass como se puede ver en las 3 primeras líneas).
Queda claro que el tratamiento es distinto y que stfld sirve para manipular tipos por valor asociados a una instancia en el managed heap, de hecho si lo piensas bien, una vez que has localizado la instancia en el managed heap por medio de la referencia … ¿porque hacer boxing/unboxing con ella? … 😛
Al igual pasa si lo hacemos con un campo estático, hay una instrucción especial para ello:
class Program
{
static Int32 intTest;
static void Main(string[] args)
{
intTest = 6;
Console.WriteLine(intTest);
}
}
CIL:
1: L_0000: ldc.i4.6
2: L_0001: stsfld int32 Test.Program::intTest
3: L_0006: ldsfld int32 Test.Program::intTest
4: L_000b: call void [mscorlib]System.Console::WriteLine(int32)
5: L_0010: ret
Como podemos ver utiliza otra instrución distinta, stsfld, para tratar con variables estáticas.
.NET Reflector da una orientación de que significa cada instrucción.
Así que vuelvo a repetir, los tipos por valor no se almacenan siempre en el thread stack como se viene diciendo en muchos sitios, solo se almacenan ahí cuando son variables locales y expresiones intermedias. El CIL tiene sus propias instruciones para acceder a tipos por valor cuando se encuentran en el managed heap, además del boxing/unboxing para almacenarlos allí directamente.
Próximo capítulo… boxing/unboxing a fondo.
Crossposting from vtortola.NET
MUY INTERESANTE, SIGUE CON ESTA SERIE.
SALU2