Antes de explicar los diferentes tipos de datos disponibles en C #, es importante mencionar que C # es un lenguaje fuertemente tipado. Esto significa que cada variable, constante, parámetro de entrada, tipo de retorno y en general cada expresión que se evalúa como un valor, tiene un tipo.
Cada tipo contiene información que será incrustada por el compilador en el archivo ejecutable como metadatos que serán utilizados por Common Language Runtime (CLR) para garantizar la seguridad del tipo cuando asigne y recupere memoria.
Si desea saber cuánta memoria asigna un tipo específico, puede usar el operador sizeof de la siguiente manera:
static void Main()
{
var size = sizeof(int);
Console.WriteLine($"int size:{size}");
size = sizeof(bool);
Console.WriteLine($"bool size:{size}");
size = sizeof(double);
Console.WriteLine($"double size:{size}");
size = sizeof(char);
Console.WriteLine($"char size:{size}");
}
La salida mostrará el número de bytes asignados por cada variable.
int size:4
bool size:1
double size:8
char size:2
La información relacionada con cada tipo es:
- El espacio de almacenamiento requerido.
- Los valores máximo y mínimo. Por ejemplo, el tipo Int32 acepta valores entre 2147483648 y 2147483647.
- El tipo base del que hereda.
- La ubicación donde se asignará la memoria para las variables en tiempo de ejecución.
- Los tipos de operaciones permitidas.
Los miembros (métodos, campos, eventos, etc.) contenidos por el tipo. Por ejemplo, si comprobamos la definición de tipo int, encontraremos la siguiente estructura y miembros:
namespace System
{
[ComVisible(true)]
public struct Int32 : IComparable, IFormattable, IConvertible, IComparable<Int32>, IEquatable<Int32>
{
public const Int32 MaxValue = 2147483647;
public const Int32 MinValue = -2147483648;
public static Int32 Parse(string s, NumberStyles style, IFormatProvider provider);
...
}
}
Gestión de la memoria
Cuando se ejecutan varios procesos en un sistema operativo y la cantidad de RAM no es suficiente para almacenarlo todo, el sistema operativo asigna partes del disco duro con la RAM y comienza a almacenar datos en el disco duro. El sistema operativo utilizará tablas específicas donde las direcciones virtuales se asignan a sus direcciones físicas correspondientes para realizar la solicitud. Esta capacidad para administrar la memoria se denomina memoria virtual.
En cada proceso, la memoria virtual disponible está organizada en las siguientes 6 secciones, pero por la relevancia de este tema, nos centraremos solo en la pila y el montón.
Pila
La pila es una estructura de datos LIFO (último en entrar, primero en salir), con un tamaño que depende del sistema operativo (de forma predeterminada, para máquinas ARM, x86 y x64, Windows reserva 1 MB, mientras que Linux reserva de 2 MB a 8 MB según versión).
Esta sección de memoria es administrada automáticamente por la CPU. Cada vez que una función declara una nueva variable, el compilador asigna un nuevo bloque de memoria tan grande como su tamaño en la pila, y cuando la función termina, el bloque de memoria para la variable se desasigna.
Montón
Esta región de memoria no es administrada automáticamente por la CPU y su tamaño es mayor que la pila. Cuando se invoca la nueva palabra clave, el compilador comienza a buscar el primer bloque de memoria libre que se ajuste al tamaño de la solicitud. y cuando lo encuentra, se marca como reservado mediante el uso de la función C incorporada malloc () y devuelve el puntero a esa ubicación. También es posible desasignar un bloque de memoria utilizando la función C incorporada free (). Este mecanismo provoca la fragmentación de la memoria y tiene que usar punteros para acceder al bloque correcto de memoria, es más lento que la pila para realizar las operaciones de lectura / escritura.
Tipos personalizados e integrados
integrados Si bien C # proporciona un conjunto estándar de tipos integrados que representan números enteros, booleanos, caracteres de texto, etc., puede usar construcciones como struct, class, interface y enum para crear sus propios tipos.
Un ejemplo de tipo personalizado que utiliza la estructura de estructura es:
struct Point
{
public int X;
public int Y;
};
Tipos de valor y referencia
Podemos clasificar el tipo de C # en las siguientes categorías:
- Tipos de valor
- Tipos de referencia
Tipos de valor Los tipos de
valor se derivan de la clase System.ValueType y las variables de este tipo contienen sus valores dentro de su asignación de memoria en la pila. Las dos categorías de tipos de valor son struct y enum.
El siguiente ejemplo muestra el miembro del tipo booleano. Como puede ver, no hay una referencia explícita a la clase System.ValueType, esto sucede porque esta clase es heredada por la estructura.
namespace System
{
[ComVisible(true)]
public struct Boolean : IComparable, IConvertible, IComparable<Boolean>, IEquatable<Boolean>
{
public static readonly string TrueString;
public static readonly string FalseString;
public static Boolean Parse(string value);
...
}
}
Tipos de referencia
Por otro lado, los tipos de referencia no contienen los datos reales almacenados en una variable, sino la dirección de memoria del montón donde se almacena el valor. Las categorías de tipos de referencia son clases, delegados, matrices e interfaces.
En tiempo de ejecución, cuando se declara una variable de tipo de referencia, contiene el valor nulo hasta que se le asigna un objeto que ha sido creado usando las palabras clave new.
El siguiente ejemplo muestra los miembros del tipo genérico List.
namespace System.Collections.Generic
{
[DebuggerDisplay("Count = {Count}")]
[DebuggerTypeProxy(typeof(Generic.Mscorlib_CollectionDebugView<>))]
[DefaultMember("Item")]
public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, IEnumerable, IList, ICollection, IReadOnlyList<T>, IReadOnlyCollection<T>
{
...
public T this[int index] { get; set; }
public int Count { get; }
public int Capacity { get; set; }
public void Add(T item);
public void AddRange(IEnumerable<T> collection);
...
}
}
En caso de que desee averiguar la dirección de memoria de un objeto específico, la clase System.Runtime.InteropServices proporciona una forma de acceder a los objetos administrados desde la memoria no administrada. En el siguiente ejemplo, usaremos el método estático GCHandle.Alloc () para asignar un identificador a una cadena y luego el método AddrOfPinnedObject para recuperar su dirección.
string s1 = "Hello World";
GCHandle gch = GCHandle.Alloc(s1, GCHandleType.Pinned);
IntPtr pObj = gch.AddrOfPinnedObject();
Console.WriteLine($"Memory address:{pObj.ToString()}");
La salida será
Memory address:39723832
Referencias
Documentación oficial: https://docs.microsoft.com/en-us/cpp/build/reference/stack-stack-allocations?view=vs-2019