Programar em C/Gerenciamento de memória

Alocação dinâmica

editar

Todos os dados de um programa são armazenados na memória do computador; é muito comum necessitar reservar um certo espaço na memória para poder guardar dados mais tarde. Por exemplo, poderíamos reservar um espaço de 1000 bytes para guardar uma string que o usuário viesse a digitar, declarando um vetor de 1000 caracteres. E se quiséssemos reservar um espaço que só é conhecido no tempo de execução do programa? E se o espaço fosse muito grande, de modo que declarar vetores de tal tamanho seria inconveniente (pois, entre outras coisas, aumenta sem necessidade o tamanho do executável)?

Para solucionar esse problema, existe a alocação dinâmica de memória, que como o nome sugere, é uma maneira de alocar memória à medida que o programa vai sendo executado. As quatro funções relacionadas com a alocação dinâmica serão descritas a seguir.

malloc e free

editar

Essas duas funções são as mais básicas para o gerenciamento de memória. malloc é responsável pela alocação de um pedaço de memória, e free é responsável por liberar esse pedaço de memória.

A função malloc() serve para alocar memória e tem o seguinte protótipo:

void *malloc (unsigned int num);
void free (void * ptr);

Para alocar um espaço na memória, precisamos fornecer à função malloc o número de bytes desejados. Ela aloca na memória e retorna um ponteiro void * para o primeiro byte alocado. O ponteiro void* pode ser atribuído a qualquer tipo de ponteiro. Se não houver memória suficiente para alocar a memória requisitada a função malloc() retorna um ponteiro nulo.

Para saber o tamanho do bloco a alocar, precisaremos usar o operador sizeof. Ele permite também saber automaticamente o tamanho de structs criadas pelo usuário.

Veja um exemplo de alocação dinâmica:

 #include <stdio.h>
 #include <stdlib.h>
   
 int main(int argc, char *argv[])
 {
   
   /* ponteiro para memória que será alocada */
   int *p;
   int i;
   
   /* alocar 10 elementos inteiros, ou seja, ( sizeof (int) * 10 ) */
   p = (int *) malloc ( sizeof (int) * 10);
         
   if ( p == NULL ) {
      printf ("Erro: Não foi possivel alocar memória\n");
      exit(1);
   }
      
   for(i = 0; i < 10; i++) {
     p[i] = i * 2;
     printf ("%d\n", p[i]);
   }
   
   /* libera a memória alocada por malloc */
   free (p);
   
   return 0;
 }

Outros exemplos:

int main()
{
   int *p, *q;
   p = malloc(sizeof(int));
   q = p;
   *p = 10;
   printf("%d\n", *q);
   *q = 20;
   printf("%d\n", *q);
}
int main()
{
    int *p, *q;
    p = malloc(sizeof(int));
    q = malloc(sizeof(int));
    *p = 10;
    *q = 20;
    *p = *q;
    printf("%d\n", *p);
}
  • O compilador aceita *p=*q porque são ambos int.
  • O compilador aceita também p=q porque ambos são ponteiros e apontam para o mesmo tipo.
  • Podemos simplificar p = malloc(sizeof(int)); por p = malloc(4); mas como temos sistemas operacionais de 16,32, 64 bits a primeira declaração torna as coisas mais portáveis.

calloc

editar

A função calloc() também serve para alocar memória, mas possui um protótipo um pouco diferente:

    void *calloc(size_t nelem, size_t elsize);

A função calloc reserva um bloco com o tamanho (nelem x elsize) octetos consecutivos, isto é, aloca memória suficiente para um vetor de num objetos de tamanho size. Diferente de malloc(), o bloco reservado é inicializado a 0. Essa função retorna um ponteiro void* para o primeiro byte alocado. O ponteiro void* pode ser atribuído a qualquer tipo de ponteiro. Se não houver memória suficiente para alocar a memória requisitada a função calloc() retorna um ponteiro nulo.

Exemplo:

#include <stdio.h>
#include <stdlib.h>  /* Para usar calloc() */

int main (){
    int *p;
    int n;
    int i;
    ...                        /* Determina o valor de n em algum lugar */
    p = calloc(n, sizeof(int));      /* Aloca n números inteiros p pode agora ser tratado como um vetor com n posicoes */
    //p = malloc(n*sizeof(int));  /* Maneira equivalente usando malloc. */
    if (!p)
    {
       	printf ("** Erro: Memoria Insuficiente **");
       	exit(0);
    }
    for (i=0; i<n; i++)        /* p pode ser tratado como um vetor com n posicoes */
         p[i] = i*i;
    ...
    return 0;
 }

No exemplo acima, é alocada memória suficiente para se colocar n números inteiros. O operador sizeof() retorna o número de bytes de um inteiro. Ele é útil para se saber o tamanho de tipos. O ponteiro void * que calloc() retorna é convertido para um int* pelo cast e é atribuído a p. A declaração seguinte testa se a operação foi bem sucedida. Se não tiver sido, p terá um valor nulo, o que fará com que !p retorne verdadeiro. Se a operação tiver sido bem sucedida, podemos usar o vetor de inteiros alocados normalmente, por exemplo, indexando-o de p[0] a p[(a-1)].

realloc

editar

A função realloc() serve para realocar memória e tem o seguinte protótipo:

   void *realloc(void *ptr, size_t size);

A função realloc ajusta o tamanho de um bloco a size octetos consecutivos. A função modifica o tamanho da memória previamente alocada com malloc, calloc ou realloc e apontada por ptr para o tamanho especificado por size. O valor de size pode ser maior ou menor que o original. Um ponteiro para o bloco é devolvido porque realloc() pode precisar mover o bloco para aumentar seu tamanho. Se isso ocorrer, o conteúdo do bloco antigo é copiado no novo bloco, o bloco antigo é liberado e nenhuma informação é perdida. Se não precisar mover, o valor retornado é igual a ptr. Se ptr for nulo, a função aloca size bytes e devolve um ponteiro, funcionando como malloc(); se size é zero, a memória apontada por ptr é liberada. Se não houver memória suficiente para a alocação, um ponteiro nulo é devolvido e o bloco original é deixado inalterado.

Exemplo:

 
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main() {
    char *str1=NULL, *str2=NULL;

    str1 = (char *) malloc(11);
    strcpy(str1, "ABCDEFGHIJ");

    str2 = (char *) realloc(str2, 20);

    printf("Endereço de str1 : %p\n", str1);
    printf("Endereço de str2 : %p\n", str2);

    str1 = (char *) realloc(str1, 100);

    printf("Novo endereço de str1 : %p\n", str1);
    printf("Conteudo de str1 : %s\n", str1);
    free(str1);
    free(str2);
    return 0;
}

Alocação Dinâmica de Vetores

editar

A alocação dinâmica de vetores utiliza os conceitos aprendidos na aula sobre ponteiros e as funções de alocação dinâmica apresentados. Um exemplo de implementação para vetor real é fornecido a seguir:

 #include <stdio.h>
 #include <stdlib.h>
 float *Alocar_vetor_real (int n)
 {
   float *v;        			           /* ponteiro para o vetor */
   if (n < 1) 
    {  			                           /* verifica parametros recebidos */
      printf ("** Erro: Parametro invalido **\n");
      return (NULL);
    }
  v = calloc (n, sizeof(float));         /* aloca o vetor */
  if (v == NULL) 
   {
     printf ("** Erro: Memoria Insuficiente **");
     return (NULL);
   }
  return (v);    			           /* retorna o ponteiro para o vetor */
 }
 float *Liberar_vetor_real (float *v)
 {
   if (v == NULL) return (NULL);
   free(v);        			          /* libera o vetor */
   return (NULL);  			          /* retorna o ponteiro */
 }
 int main (void)
 {
   float *p;
   int a;
   ...    				/* outros comandos, inclusive a inicializacao de a 

*/
   p = Alocar_vetor_real (a);
   ...    				/* outros comandos, utilizando p[] normalmente */
   p = Liberar_vetor_real (p);
 }

Alocação Dinâmica de Matrizes

editar

A alocação dinâmica de memória para matrizes é realizada da mesma forma que para vetores, com a diferença que teremos um ponteiro apontando para outro ponteiro que aponta para o valor final, ou seja é um ponteiro para ponteiro, o que é denominado indireção múltipla. A indireção múltipla pode ser levada a qualquer dimensão desejada, mas raramente é necessário mais de um ponteiro para um ponteiro. Um exemplo de implementação para matriz real bidimensional é fornecido a seguir. A estrutura de dados utilizada neste exemplo é composta por um vetor de ponteiros (correspondendo ao primeiro índice da matriz), sendo que cada ponteiro aponta para o início de uma linha da matriz. Em cada linha existe um vetor alocado dinamicamente, como descrito anteriormente (compondo o segundo índice da matriz).

#include <stdio.h>
#include <stdlib.h>

float **Alocar_matriz_real (int m, int n)
{
    float **v;                             /* ponteiro para a matriz */
    int   i;                               /* variavel auxiliar      */
    if (m < 1 || n < 1) 
    {                                      /* verifica parametros recebidos */
        printf ("** Erro: Parametro invalido **\n");
         return (NULL);
    }                                   /* aloca as linhas da matriz */
    v = calloc (m, sizeof(float *));      /*Um vetor de m ponteiros para float */
    if (v == NULL)
    {
         printf ("** Erro: Memoria Insuficiente **");
         return (NULL);
    }					
    for ( i = 0; i < m; i++ )           /* aloca as colunas da matriz */
    {
         v[i] = calloc (n, sizeof(float));    /* m vetores de n floats */
         if (v[i] == NULL) 
         {
             printf ("** Erro: Memoria Insuficiente **");
             return (NULL);
         }
    }
    return (v); 					/* retorna o ponteiro para a matriz */
}

float **Liberar_matriz_real (int m, int n, float **v)
{
    int  i;                       /* variavel auxiliar */
    if (v == NULL) return (NULL);
    if (m < 1 || n < 1) 
    {                      /* verifica parametros recebidos */
        printf ("** Erro: Parametro invalido **\n");
        return (v);
    }
    for (i=0; i<m; i++) free (v[i]);    /* libera as linhas da matriz */
    free (v);                         /* libera a matriz (vetor de ponteiros) */
    return (NULL);                  /* retorna um ponteiro nulo */
}

int main (void)
{
    float **mat;       /* matriz a ser alocada */
    int   l, c;         /* numero de linhas e colunas da matriz */
    int i, j;
    ...                 /* outros comandos, inclusive inicializacao para l e c */
    mat = Alocar_matriz_real (l, c);
    for (i = 0; i < l; i++)
      for ( j = 0; j < c; j++)
         mat[i][j] = i+j;
    ...                    /* outros comandos utilizando mat[][] normalmente */
    mat = Liberar_matriz_real (l, c, mat);
    ...
}