Antes de explicar os diferentes tipos de dados disponíveis em C #, é importante mencionar que C # é uma linguagem fortemente tipada. Isso significa que cada variável, constante, parâmetro de entrada, tipo de retorno e, em geral, toda expressão que avalia um valor, tem um tipo.
Cada tipo contém informações que serão incorporadas pelo compilador ao arquivo executável como metadados que serão usados pelo Common Language Runtime (CLR) para garantir a segurança do tipo ao alocar e recuperar memória.
Se você quiser saber quanta memória um tipo específico aloca, pode usar o operador sizeof da seguinte maneira:
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}");
}
A saída mostrará o número de bytes alocados por cada variável.
int size:4
bool size:1
double size:8
char size:2
As informações relacionadas a cada tipo são:
- O espaço de armazenamento necessário.
- Os valores máximo e mínimo. Por exemplo, o tipo Int32 aceita valores entre 2147483648 e 2147483647.
- O tipo básico do qual ele herda.
- O local onde a memória para variáveis será alocada em tempo de execução.
- Os tipos de operações permitidas.
Os membros (métodos, campos, eventos, etc.) contidos pelo tipo. Por exemplo, se verificarmos a definição do tipo int, encontraremos a seguinte estrutura e membros:
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);
...
}
}
Gerenciamento de memória
Quando vários processos estão sendo executados em um sistema operacional e a quantidade de RAM não é suficiente para mantê-los todos, o sistema operacional mapeia partes do disco rígido com a RAM e começa a armazenar dados no disco rígido. O sistema operacional usará outras tabelas específicas onde os endereços virtuais são mapeados para seus endereços físicos correspondentes para realizar a solicitação. Essa capacidade de gerenciar a memória é chamada de memória virtual.
Em cada processo, a memória virtual disponível é organizada nas 6 seções a seguir, mas pela relevância deste tópico, vamos nos concentrar apenas na pilha e no heap.
Pilha
A pilha é uma estrutura de dados LIFO (último a entrar, primeiro a sair), com um tamanho dependente do sistema operacional (por padrão, para máquinas ARM, x86 e x64, o Windows reserva 1 MB, enquanto o Linux reserva de 2 MB a 8 MB dependendo do versão).
Esta seção da memória é gerenciada automaticamente pela CPU. Cada vez que uma função declara uma nova variável, o compilador aloca um novo bloco de memória tão grande quanto seu tamanho na pilha e, quando a função termina, o bloco de memória para a variável é desalocado.
Heap
Esta região da memória não é gerenciada automaticamente pela CPU e seu tamanho é maior que a pilha. Quando a nova palavra-chave é chamada, o compilador começa a procurar o primeiro bloco de memória livre que se ajusta ao tamanho da solicitação. e quando o encontra, é marcado como reservado usando a função C embutida malloc () e retorna o ponteiro para aquele local. Também é possível desalocar um bloco de memória usando a função C free () embutida. Este mecanismo causa fragmentação da memória e tem que usar ponteiros para acessar o bloco certo de memória, é mais lento que a pilha para realizar as operações de leitura / gravação.
Tipos personalizados e
integrados Embora o C # forneça um conjunto padrão de tipos integrados que representam inteiros, booleanos, caracteres de texto e assim por diante, você pode usar construções como struct, classe, interface e enum para criar seus próprios tipos.
Um exemplo de tipo personalizado usando a construção de estrutura é:
struct Point
{
public int X;
public int Y;
};
Tipos de valor e referência
Podemos categorizar o tipo C # nas seguintes categorias:
- Tipos de valor
- Tipos de referência
Tipos de valor Os tipos de
valor derivam da classe System.ValueType e as variáveis desse tipo contêm seus valores na alocação de memória na pilha. As duas categorias de tipos de valor são struct e enum.
O exemplo a seguir mostra o membro do tipo boolean. Como você pode ver, não há referência explícita à classe System.ValueType, isso acontece porque essa classe é herdada pela estrutura.
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 referência
Por outro lado, os tipos de referência não contêm os dados reais armazenados em uma variável, mas o endereço da memória do heap onde o valor está armazenado. As categorias de tipos de referência são classes, delegados, arrays e interfaces.
Em tempo de execução, quando uma variável do tipo de referência é declarada, ela contém o valor null até que um objeto que foi criado usando as palavras-chave new seja atribuído a ela.
O exemplo a seguir mostra os membros do 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);
...
}
}
Caso você queira descobrir o endereço de memória de um objeto específico, a classe System.Runtime.InteropServices fornece uma maneira de acessar objetos gerenciados da memória não gerenciada. No exemplo a seguir, vamos usar o método estático GCHandle.Alloc () para alocar um identificador para uma string e, em seguida, o método AddrOfPinnedObject para recuperar seu endereço.
string s1 = "Hello World";
GCHandle gch = GCHandle.Alloc(s1, GCHandleType.Pinned);
IntPtr pObj = gch.AddrOfPinnedObject();
Console.WriteLine($"Memory address:{pObj.ToString()}");
A saída será
Memory address:39723832
Referências
Documentação oficial: https://docs.microsoft.com/en-us/cpp/build/reference/stack-stack-allocations?view=vs-2019