
Em muitas aplicações não é possível saber a quantidade de dados a ser manipulada em um programa. Consequentemente, a quantidade de memória a alocar só é conhecida durante a execução do programa.
Neste caso, a alocação estática de memória não é eficiente pois não é possível alterar o tamanho de uma função após a compilação de um programa. Para tal, seria necessário alterar o código e recompilar a função em que se deseje alterar o seu tamanho.
Quando utilizada a alocação estática de memória em aplicações em que não se sabe a quantidade de dados a ser manipulada existe o risco de alocar muito espaço e utilizar apenas uma parte dele ou de não alocar a quantidade de memória suficiente para uma quantidade de dados que se necessita manipular.
Para lidar com essa situação é preciso recorrer à alocação dinâmica de memória. A alocação dinâmica de memória permite decidir qual o tamanho de espaço de memória a ser alocado durante a execução do programa.
Este espaço de memória é alocado em uma parte do segmento de um processo chamado de heap.
Na alocação dinâmica a alocação é feita através de uma chamada de função do sistema operacional. Em linguagem C existem duas chamadas de funções principais para a manipulação de memória: malloc() e free().
malloc
Aloca um bloco de bytes de tamanho de memória na heap, retornando um ponteiro para o início do bloco.
onde:
- void é um tipo coringa que tem tamanho igual a zero;
- void* o asterisco define o tipo ponteiro que permite armazenar endereços de memória e especificar o tipo do dado armazenado neste endereço;
- size_t é um inteiro que aceita apenas números maiores ou iguais a zero;
- size é o tamanho do bloco alocado em bytes.
Exemplo de uso da função malloc e o comportamento da memória (Tabela 01):
Tabela 01 – Pilha de Memória na Alocação Dinâmica: função malloc.
Tipo | Nome | Pilha | Heap | Tamanho | ||
---|---|---|---|---|---|---|
Endereço | Estado | Endereço | Estado | |||
– | – | #00FE1A | – | #00FF0A | – | 1 byte |
– | – | #00FE19 | – | #00FF09 | reservado | 1 byte |
– | – | #00FE18 | – | #00FF08 | reservado | 1 byte |
– | – | #00FE17 | – | #00FF07 | reservado | 1 byte |
– | – | #00FE16 | – | #00FF06 | reservado | 1 byte |
– | – | #00FE15 | – | #00FF05 | reservado | 1 byte< |
– | – | #00FE14 | – | #00FF04 | – | 1 byte |
void | var1 | #00FE13 | #00FF05 | #00FF03 | – | 1 byte |
– | – | #00FE12 | – | #00FF02 | – | 1 byte |
– | – | #00FE11 | – | #00FF01 | – | 1 byte |
Fonte: Autor.
free
Desaloca um bloco de memória previamente alocado por uma chamada a malloc, calloc ou realloc, tornando-o disponível novamente para novas alocações.
onde:
- void é um tipo coringa que tem tamanho igual a zero;
- void* o asterisco define o tipo ponteiro para o parâmetro que deve receber o endereço de memória que contém o tipo do dado especificado que foi armazenado neste endereço;
- ptr é o ponteiro que contém o endereço alocado em bytes.
Exemplo de uso da função free e o comportamento da memória (Tabela 02):
Tabela 02 – Pilha de Memória na Alocação Dinâmica: função free.
Tipo | Nome | Pilha | Heap | Tamanho | ||
---|---|---|---|---|---|---|
Endereço | Estado | Endereço | Estado | |||
– | – | #00FE1A | – | #00FF0A | – | 1 byte |
– | – | #00FE19 | – | #00FF09 | – | 1 byte |
– | – | #00FE18 | – | #00FF08 | – | 1 byte |
– | – | #00FE17 | – | #00FF07 | – | 1 byte |
– | – | #00FE16 | – | #00FF06 | – | 1 byte |
– | – | #00FE15 | – | #00FF05 | – | 1 byte |
– | – | #00FE14 | – | #00FF04 | – | 1 byte |
void | var1 | #00FE13 | #00FF05 | #00FF03 | – | 1 byte |
– | – | #00FE12 | – | #00FF02 | – | 1 byte |
– | – | #00FE11 | – | #00FF01 | – | 1 byte |
Fonte: Autor.
Em C, existem também mais duas derivadas destas funções: calloc() e realloc(). Fica aqui recomendado ao leitor a exploração de seus propósitos e aplicações.
Acesso aos Dados e aos Endereços de Memória
Exemplo de acesso a memória e o seu comportamento (Tabela 03):
onde:
- int é um tipo inteiro que tem tamanho igual a 4 bytes;
- int* o asterisco define o tipo ponteiro para o parâmetro que deve receber o endereço de memória que contém o tipo do dado especificado que foi armazenado neste endereço;
- (int*) conhecido como operador de coerção (cast), converte o endereço do tipo (void*) retornado pela função malloc() em um ponteiro para um tipo inteiro. A coerção não altera o endereço, mas apenas como que o registro apontado é interpretado;
- sizeof() é uma função que retorna o tamanho, em bytes, de um determinado tipo;
- *var1 o operador asterisco permite atribuir um valor na posição apontada pelo endereço da heap armazenado na pilha em var1;
Tabela 03 – Pilha de Memória na Alocação Dinâmica: acesso a memória.
Tipo |
Nome |
Pilha | Heap | Tamanho |
||
---|---|---|---|---|---|---|
Endereço | Estado | Endereço | Estado | |||
– | – | #00FE1A | – | #00FF0A | – | 1 byte |
– | – | #00FE19 | – | #00FF09 | – | 1 byte |
– | – | #00FE18 | – | #00FF08 | 10 | 1 byte |
– | – | #00FE17 | – | #00FF07 | 1 byte | |
– | – | #00FE16 | – | #00FF06 | 1 byte | |
– | – | #00FE15 | – | #00FF05 | 1 byte | |
– | – | #00FE14 | – | #00FF04 | – | 1 byte |
int | var1 | #00FE13 | #00FF05 | #00FF03 | – | 1 byte |
– | – | #00FE12 | – | #00FF02 | – | 1 byte |
– | – | #00FE11 | – | #00FF01 | – | 1 byte |
Fonte: Autor.
Em C, o acesso aos dados e aos endereços de memória onde estão contidos podem ser feitos através de:
- &var1 acessa o endereço da pilha onde é armazenado o endereço da heap;
- var1 acessa o endereço do bloco alocado na heap;
- *var1 acessa o valor armazenado na heap.
Operador | Tipo | Valor |
---|---|---|
&var1 | (int*)* | #00FE13 |
var1 | int* | #00FF05 |
*var1 | int | 10 |
Estratégias de Alocação de Memória
Cada estratégia de alocação de memória supri uma demanda de gestão da memória para cada tipo de aplicação, ao mesmo tempo em que gera novas demandas a serem supridas, não existindo necessariamente uma relação de superioridade entre cada uma delas. Isso significa que a estratégia de alocação de memória adotada pelo programador depende de sua adequação à natureza da aplicação a ser desenvolvida.
Complexidade vs Tamanho da função (ou bloco)
A alocação estática de memória é mais simplificada que a alocação dinâmica de memória. Contudo, não é possível alterar o tamanho de uma função após a compilação de um programa. Para tal, seria necessário alterar o código e recompilar a função em que se deseje alterar o seu tamanho.
Já na alocação dinâmica de memória é possível alterar o tamanho de uma função após a compilação de um programa sem a necessidade de alterar o código e recompilar a função em que se deseje alterar o seu tamanho. Contudo, para isso esta estratégia necessita de funções para o gerenciamento de memória que aumentam a sua complexidade. Além de que, esta característica especifica da alocação dinâmica demanda mais atenção do programador, pois cada espaço alocado explicitamente em seu código deve ser acompanhado de uma chamada de função para sua desalocação. Isso implica no risco de não haver o gerenciamento adequado de memória e, consequentemente, não haver o aproveitamento adequado do espaço de memória, seja porque não foram liberados os espaços de memória previamente alocados ou por haver uma segmentação dos espaços de memória de forma que exista um limite do tamanho dos blocos que podem ser alocados.
Uso da Memória
Na alocação estática os espaços de memória são alocados em blocos sucessivos na pilha de memória, logo existe uma relação entre os espaços alocados, o que facilita a localização e coleta da informação alocada, pois elas estão “empilhadas” na pilha.
Já na alocação dinâmica, ao se realizar subsequentes chamadas de funções de alocação, não há garantia de que os espaços de memória entre uma chamada e a outra tenham qualquer relação pois o espaço alocado pode está em qualquer posição da heap, o que torna mais difícil a localização e coleta da informação alocada.
Referências
- PIVA JÚNIOR, Dilermando (et al). Estrutura de dados e técnicas de programação. 1. ed. Rio de Janeiro, RJ: Campus, 2014. 399 p. ISBN: 9788535274370.
- CORMEN, Thomas H et al. Algoritmos: teoria e prática. Rio de Janeiro: Elsevier, 926 p. ISBN: 9788535236996.
- TOSCANI, Laira Vieira; VELOSO, Paulo A. S. Complexidade de algoritmos: análise, projeto e métodos. 3. ed. Porto Alegre: Bookman, 2012. 261 p. (Série livros didáticos informática UFRGS, 13) ISBN: 9788540701380.
- ASCENCIO, Ana Fernanda Gomes. Estruturas de dados: algoritmos, análise da complexidade e implementações em Java e C/C++. São Paulo: Pearson, c2010. 432 p. ISBN: 9788576052216, 978857605816.
- BRASSARD, Gilles; BRATLEY, Paul. Fundamentals of algorithmics. Englewood Cliffs: Prentice Hall, c1996. xx, 524 p. ISBN: 0133350681.