Alocação dinâmica de memória

From Applied Science

Funções e variáveis ocupam um espaço na memória do computador quando são declaradas. No nível introdutório não vemos como essa memória é gerenciada, isso é algo que o sistema operacional faz sozinho. Há classes de algoritmos que lidam com gerenciamento de memória, mas não os estudamos numa introdução.

Há muitas questões naturais que surgem a respeito de como o sistema operacional gerencia a memória: "Onde na memória as variáveis são guardadas?", "Onde na memória as funções são guardadas?", "Um programa pode invadir o espaço de memória de outro programa?", Pode uma variável ser sobrescrita por outra na memória?", "O que acontece se o programa precisa de mais memória do que a quantidade física disponível?", "A operação de alocar e liberar a memória custam processamento?", etc. Para entender isso você precisa estudar como funciona um sistema operacional.

No capítulo sobre vetores e matrizes aprendemos que C não permite que o índice de um vetor seja uma variável. Ele precisa ser uma constante e não pode mudar durante a execução do programa. Quando declaramos variáveis ou vetores a memória é alocada assim que o programa executa e precisa ter um tamanho previamente conhecido. Existe uma função que permite alocar dinamicamente vetores e matrizes de tamanho variável.

Erros de lógica:

  • Alocar a memória e perder o endereço daquela memória (perder o ponteiro), o que torna os dados ali guardados inacessíveis;
  • Alocar a memória e não liberá-la depois que não é mais usada. Isso é o chamado "vazamento de memória".


Os exemplos precisam de entendimento de funções e ponteiros
  • Alocação dinâmica de um vetor:
/* Declarar o ponteiro e fazê-lo apontar para um bloco de memória com o tamanho de 100 inteiros */
int *p;
p = (int *) malloc(100*sizeof(int));

operador sizeof: ele calcula o tamanho em bytes do que aparece depois dele. Visualmente parece uma função por causa dos parêntesis, mas isso é por causa da precedência do operador. Na prática não precisaríamos dos parêntesis, mas fica melhor de ler assim.

função malloc: o argumento desta função é o número de bytes que serão alocatos. No exemplo acima o tamanho eram 100 inteiros. Malloc é uma função que devolve um ponteiro para o início do bloco de memória alocado. No caso, estamos declarando um ponteiro p e fazendo-o receber o endereço devolvido por malloc.

conversão de tipo: uma pergunta surge aqui "malloc devolve um ponteiro de que tipo?". Precisamos de uma operação "cast" usando (int *) para garantir que estamos atribuindo um ponteiro para inteiro para outro ponteiro do mesmo tipo. Não é uma obrigatoriedade, depende do compilador. Cuidado com o parêntesis. Se tirar os parêntesis o compilador irá ler que malloc em si é um ponteiro.

Sobrecarga (overhead): a alocação dinâmica não é "de graça", incorre um custo extra de memória usada pelo sistema para gerenciar a alocação da memória durante a execução do programa. De uma maneira geral é melhor ter menos chamadas de malloc para grandes blocos de memória do que menos chamadas para blocos menores.

Nota: é impossível o programa "adivinhar" a quantidade de memória necessária se os dados que entram podem variar de muito grande a muito pequeno. Suponha que um vetor irá guardar os caracteres do nome de uma pessoa. Podemos forçar o usuário a digitar o número de caracteres do seu próprio nome, incluindo espaços em branco, ou deixar que malloc reserve memória suficiente para um vetor guardar o maior nome possível.