Alocação dinâmica de memória

[head-sticky-notes.gif]

As declarações abaixo alocam espaço na memória para algumas variáveis. A alocação é estática (nada a ver com a palavra-chave static), ou seja, acontece antes que o programa comece a ser executado:

char c; 
int i; 
int v[10]; 

Em muitas aplicações, a quantidade de memória a alocar só se torna conhecida durante a execução do programa. Para lidar com essa situação é preciso recorrer à alocação dinâmica de memória. A alocação dinâmica é administrada pelas funções malloc, realloc e free, que estão na biblioteca stdlib.  Para usar essa biblioteca, inclua a correspondente interface no seu programa:

#include <stdlib.h>

Sumário:

A função malloc

A função  malloc  (o nome é uma abreviatura de memory allocation) aloca espaço para um bloco de bytes consecutivos na memória RAM (= random access memory) do computador e devolve o endereço desse bloco.  O número de bytes é especificado no argumento da função. No seguinte fragmento de código, malloc aloca 1 byte:

char *ptr;
ptr = malloc (1);
scanf ("%c", ptr);

O endereço devolvido por malloc é do tipo genérico  void *.  O programador armazena esse endereço num ponteiro de tipo apropriado. No exemplo acima, o endereço é armazenado no ponteiro ptr, que é do tipo ponteiro-para-char.  A transformação do ponteiro genérico em ponteiro-para-char é automática; não é necessário escrever ptr = (char *) malloc (1);.

A fim de alocar espaço para um objeto que precisa de mais que 1 byte, convém usar o operador sizeof, que diz quantos bytes o objeto em questão tem:

typedef struct {
   int dia, mes, ano; 
} data;
data *d;
d = malloc (sizeof (data));
d->dia = 31; d->mes = 12; d->ano = 2016;

Note que sizeof não é uma função mas um operador, tal como return, por exemplo. Os parênteses na expressão sizeof (data) são necessários porque data é um tipo-de-dados  (os parênteses são análogos aos do casting).  O operador sizeof também pode ser aplicado diretamente a uma variável:  se var é uma variável então  sizeof var  é o número de bytes ocupado por var.  Poderíamos ter escrito d = malloc (sizeof *d) no exemplo acima.

Exercícios 1

  1. Escreva uma função que receba um byte c (que pode representar um caractere ASCII, por exemplo) e transforme-o em uma string, ou seja, devolva uma string de comprimento 1 tendo c como único elemento.
  2. Discuta, passo a passo, o efeito do seguinte fragmento de código:
    int *v;
    v = malloc (10 * sizeof (int));
    
  3. Discuta o efeito do seguinte fragmento de código:
    x = malloc (10 * sizeof *x);
    

A função free

As variáveis alocadas estaticamente dentro de uma função, também conhecidas como variáveis automáticas ou locais, desaparecem assim que a execução da função termina. Já as variáveis alocadas dinamicamente continuam a existir mesmo depois que a execução da função termina. Se for necessário liberar a memória ocupada por essas variáveis, é preciso recorrer à função free.

A função free desaloca a porção de memória alocada por malloc. A instrução free (ptr) avisa ao sistema que o bloco de bytes apontado por ptr está disponível para reciclagem. A próxima invocação de malloc poderá tomar posse desses bytes.

Nunca aplique a função free a uma parte de um bloco de bytes alocado por malloc (ou realloc). Aplique free apenas ao bloco todo.

Exercícios 2

  1. O que há de errado com o seguinte fragmento de código?
    int *v;
    v = malloc (100 * sizeof (int));
    v[0] = 999;
    free (v+1);
    
  2. A seguinte função promete devolver um vetor com os 3 primeiros números primos maiores que 1000. Onde está o erro?
    int *primos (void) {
       int v[3];
       v[0] = 1009; v[1] = 1013; v[2] = 1019;
       return v; }
    
  3. A seguinte função promete acrescentar uma célula-cabeça à lista encadeada lst e devolver o endereço da nova lista. Onde está o erro?
    celula *acrescentaCabeca (celula *lst) {
       celula cabeca;
       cabeca.prox = lst;
       return &cabeca; }
    
  4. Discuta, passo a passo, o efeito do seguinte fragmento de código:
    int *p, *q;
    p = malloc (sizeof (int));
    *p = 123;
    q = malloc (sizeof (int));
    *q = *p;
    q = p; 
    free (p);
    free (q); // má ideia...
    q = NULL; // boa ideia
    

Vetores e matrizes

Eis como um vetor (= array) com n elementos inteiros pode ser alocado (e depois desalocado) durante a execução de um programa:

int *v; 
int n;
scanf ("%d", &n);
v = malloc (n * sizeof (int));
for (int i = 0; i < n; ++i) 
   scanf ("%d", &v[i]);
. . . 
free (v);

(A propósito, veja a seção Aritmética de endereços no capítulo Endereços e ponteiros.)  Do ponto de vista conceitual, mas apenas desse ponto de vista, a instrução

v = malloc (100 * sizeof (int));

tem efeito análogo ao da alocação estática

int v[100];

Matrizes.  Matrizes bidimensionais são implementadas como vetores de vetores. Uma matriz com m linhas e n colunas é um vetor de m elementos cada um dos quais é um vetor de n elementos. O seguinte fragmento de código faz a alocação dinâmica de uma tal matriz:

int **M; 
M = malloc (m * sizeof (int *));
for (int i = 0; i < m; ++i)
   M[i] = malloc (n * sizeof (int));

Portanto, M[i][j] é o elemento de M que está no cruzamento da linha i com a coluna j.

Exercícios 3

  1. Escreva uma função que desaloque a matriz M alocada acima. Quais devem ser os parâmetros da função?

Redimensionamento e a função realloc

Às vezes é necessário alterar, durante a execução do programa, o tamanho de um bloco de bytes que foi alocado por malloc.  Isso acontece, por exemplo, durante a leitura de um arquivo que se revela maior que o esperado.  Nesse caso, podemos recorrer à função realloc para redimensionar o bloco de bytes.

A função realloc aumenta o tamanho de um bloco de memória que tenha sido alocado dinamicamente. A função recebe o endereço de um bloco anteriormente alocado por malloc (ou pelo próprio realloc) bem como o número de bytes que o novo bloco deve ter. A função aloca um bloco do tamanho desejado, copia para ele o conteúdo do bloco original, e devolve o endereço do novo bloco. A função libera o bloco original invocando free.

A função realloc também pode diminuir o tamanho de um bloco de memória alocado dinamicamente. É o que acontece se o número de bytes solicitado for menor que o tamanho do bloco original. Nesse caso, não há alocação de memória nem movimentação do conteúdo do bloco, apenas redução do tamanho do bloco. O endereço do novo bloco é o mesmo do original.

Suponha, por exemplo, que alocamos um vetor de 1000 inteiros e depois decidimos que precisamos de duas vezes mais espaço. Veja um caso concreto:

int *v;
v = malloc (1000 * sizeof (int));
for (int i = 0; i < 990; i++)
   scanf ("%d", &v[i]);
// ops! precisamos de mais
v = realloc (v, 2000 * sizeof (int));
for (int i = 990; i < 2000; i++)
   scanf ("%d", &v[i]);

Nesse exemplo, poderíamos usar a seguinte implementação ad hoc de realloc:

int *realloc (int *v, unsigned int N) {
   int *novo = malloc (N);
   for (int i = 0; i < 1000; i++)
      novo[i] = v[i];
   free (v);
   return novo;
}

É claro que a implementação de realloc na biblioteca stdlib é mais geral e mais eficiente.

Exercícios 4

  1. Suponha dado um arquivo de texto que contém uma sequência de números inteiros. O comprimento da sequência é desconhecido. Escreva uma função que imprima esses números em ordem inversa (o último, depois o penúltimo, etc.).  É claro que você terá que ler todos os números e armazená-los na memória.  A dificuldade está em alocar espaço para uma quantidade de números que só será conhecida quando chegarmos ao fim do arquivo.
  2. Suponha dado um arquivo de texto que contém uma sequência de números inteiros. O comprimento da sequência é desconhecido. Escreva uma função que armazene esses números em um vetor cujo tamanho seja o mínimo necessário. (Leia o arquivo uma só vez!)

A memória é finita

Se a memória do computador já estiver toda ocupada, malloc não consegue alocar mais espaço e devolve NULL. Convém verificar essa possibilidade antes de prosseguir:

ptr = malloc (sizeof (data));
if (ptr == NULL) {
   printf ("Socorro! malloc devolveu NULL!\n");
   exit (EXIT_FAILURE);
}  

A digitação frequente e repetida desse teste é cansativa. Por isso, usaremos, neste sítio, a seguinte função-embalagem (= wrapper function) de malloc:

void *mallocc (size_t nbytes) {
   void *ptr;
   ptr = malloc (nbytes);
   if (ptr == NULL) {
      printf ("Socorro! malloc devolveu NULL!\n");
      exit (EXIT_FAILURE);
   }
   return ptr;
}

O parâmetro de mallocc é do tipo  size_t  (abreviatura de size data type); em muitos computadores, size_t é o mesmo que unsigned int.

Da mesma forma, podemos preparar uma função-embalagem reallocc para cuidar das situações em que realloc devolve NULL.


Perguntas e respostas