Pular para o conteúdo principal

Memory Allocation

Thiago Lages de Alencar

Alocação de memória se trata de pedir ao sistema operacional por espaço de memória RAM para utilizarmos durante a execução do nosso programa.

note

Não confundir com dispositivos de armazenamentos como HDD e SSD, onde nossa interação com eles customa ser por escrita e leitura de arquivos.

Static Allocation

Se refere a alocar espaço para todos os dados que já se sabe que serão necessários desdo início do seu programa.

O espaço necessário é descoberto durante a compilação de um programa e armazenado em conjunto do binário para que já seja carregada na memória na inicialização do programa.

Compilador irá identificar:

  • Literais
  • Variáveis estáticas/globais
  • Código de Funções

Pegando o seguinte código como exemplo:

static int executions = 0;

void run(int p) {
printf("Running");
executions += 1;
}

int main(void) {
run(1);
run(5);
run(10);
return 0;
}

Para que seu programe funcione, o compilador consegue identificar que será necessário espaço para o literal "Running", variável estática executions e o código da função run().

A memória alocada é separada em dois segmentos:

  • Data Segment
    • Variáveis estáticas
  • Text Segment
    • Literais
    • Código de funções

Data segments funcionam como espaço de memória normal, onde podem ter seus valores atualizado/modificados.

Text segments são armazenados uma vez e apenas utilizados para leitura durante a execução do seu programa.


Variáveis estáticas caem na primeira categoria pois a qualquer momento podemos fazer algo como executions += 1.

Literais caem na segunda pois sempre precisaremos daquele exato literal quando o código passar por aquela linha de código printf("Running"), então não queremos que ele seja modificado de maneira nenhuma.

Código de funções são somente leitura pois estamos falando da base para se criar funções conforme o necessário. O que eu quero dizer com isto? Toda vez que executarmos uma função, utilizaremos o código da função como base para alocar memória para aquela execução da função!

Por que não fazer com que todas as chamadas da funções utilizem o mesmo espaço? Cada chamada pode ter comportamento diferente por causa de parâmetros ou fatores externos. Isto quer dizer arriscariamos colisão entre as execuções, o que poderia trazer resultados diferentes.

Imagine que seu código possue uma função recursiva, agora você corre o risco das chamadas a ela mesma alterarem uma variável que era essencial dela.

Stack Allocation

Se refere a alocação feita no início do programa para dados temporários ou curto tempo de vida. Este espaço reservado é chamado de Stack.

Isto é necessário pois nosso código pode se ramificar de diversas maneiras, tornando impossível descobrir toda a memória que será utilizada durante a etapa de compilação.

Pegando o seguinte código como exemplo:

int func1(int a, int b) {
return a + b;
}

int func2(int a, int b) {
int x = a * 3;
int y = b * 2;
return x + y;
}

int run(int a, int b) {
if(a > 10) {
return func1();
} else {
return func2();
}
}

Quando uma função é chamada, o programa insere na stack variáveis daquela função.

No caso da func1(): a, b.
No caso da func2(): a, b, x, y.

Ao sair da função, o programa remove esse valor da stack.

É importante notar que como o espaço da stack já foi alocada no início do programa, inserir e remover da stack são operações rápidas.

Quando CPUs precisam de dados da memória RAM, elas pegam um bloco de dados de cada vez. O ideal é que nessa pegada já tivesse tudo que a CPU precisaria, para ajudar nisto stacks seguem o modelo (LIFO, last-in, first-out).

Pegando a func2() como exemplo:

Stack
a
b
x
y

Podemos notar que inserimos na stack na ordem em que encontramos as variáveis, justamente para quando a CPU pegar um bloco de memória a chance de pegar tudo aumentar.

Por outro lado note que essa arquitetura impede que nossas variáveis possam crescer de tamanho (b não poderia crescer de tamanho pois o espaço seguinte já está reservado por x).

Explicit Memory Management

Se refere a alocação feita durante a execução do programa para dados de tamanhos variados. Este espaço reservado é chamado de Heap.

int run(int n) {
int *v = (int*)malloc(sizeof(int) * n);
v[0] = 10;
v[1] = 100;
v[2] = 1000;
v[3] = 10000;
free(v);

return 0;
}

Garbage Collection

Reference Counting

Ownership Model

References