• Sempre que escrevemos um programa, é preciso reservar espaço para as informações que serão processadas. Para isso utilizamos as variáveis:

    • Uma variável é uma posição de memória que armazena uma informação que pode ser modificada pelo programa. Ela deve ser definida antes de ser usada;
  • Infelizmente, nem sempre é possível saber, em tempo de execução, o quanto de memória um programa irá precisar;

  • Por Exemplo:

    • Imagine que você precise construir um programa que processe os valores dos salários dos funcionários de uma pequena empresa. PS.: Eu não sei o número de funcionários da empresa.

      //Solução inicial
      	
      #include <stdio.h>
      #include <stdlib.br>
      
      int main (){
      	float salario [1000];
      	system ("pause");
      	return 0;
      }
      
    • Posso ter dois inconvenientes:

      • Ter mais de 1000 funcionários;
      • Ter apenas 10 funcionários (990 posições de memória desperdiçadas);
  • Um programa em C suporta três tipos de alocação de memória:

    • A alocação estática ocorre quando são declaradas variáveis globais ou estáticas; geralmente alocadas em Data.
    • A alocação automática ocorre quando são declaradas variáveis locais e parâmetros de funções. O espaço para a alocação dessas variáveis é reservado quando a função é invocada, e liberado quando a função termina. Geralmente é usada a pilha (stack).
    • A alocação dinâmica, quando o processo requisita explicitamente um bloco de memória para armazenar dados; o controle das áreas alocadas dinamicamente é manual ou semi-automático: o programador é responsável por liberar as áreas alocadas dinamicamente. A alocação dinâmica geralmente usa a área de heap.
  • Vamos considerar o seguinte:

    • Arrays são agrupamentos sequenciais de dados de um mesmo tipo na memória;
    • Um ponteiro guarda o endereço de um dado na memória;
    • O nome do array é um ponteiro para o primeiro elemento do array;
  • Posso solicitar um bloco de memória e colocar a sua primeira posição em um ponteiro?

    • A linguagem C permite alocar (reservar) dinamicamente (em tempos de execução) blocos de memória. Logo a alocação dinâmica permite ao programador “criar” arrays em tempo de execução, ou seja, alocar memória para novos arrays quando o programa está sendo executado, e não apenas quando se está escrevendo o programa;
    • Assim quando não se sabe ao certo quanto de memória será necessário para armazenar os dados, pode-se definir o tamanho do array em tempo de execução, evitando assim o desperdício de memória;
  • De modo geral quando você inicializa um programa o sistema operacional mantém 4 “pedaços” distintos de memória para seu programa.

    Untitled

  • A alocação dinâmica de memória é um recurso importante em linguagens de programação que permite que você alocar e liberar memória durante a execução de um programa. Isso é útil quando você não conhece o tamanho exato de um objeto ou quando deseja controlar explicitamente a vida útil da memória.

  • A alocação dinâmica de memória é um mecanismo utilizado para possibilitar que uma quantidade de memória seja reservada durante o tempo de execução de uma aplicação. A região de memória utilizada para alocação dinâmica é conhecida como heap.

  • A linguagem C usa apenas 4 funções para o sistema de alocação dinâmica, disponíveis na stdlib.h:

    • malloc -aloca o número especificado de bytes;
    • calloc - aloca o número especificado de bytes e os inicializa para zero;
    • realloc - aumenta ou diminui o tamanho do bloco de memória especificado, movendo-o se necessário;
    • free - libera o bloco especificado de memória de volta para o sistema;
    • Um operador (auxilia as demais funções na alocação de memória): Sizeof;
  • Malloc() - memory allocation

    • A função malloc() serve para alocar memória durante a execução do programa. É responsável pelo pedido de memória ao computador e retorna um ponteiro com o endereço do inicio do espaço de memória alocado;

      • void *malloc (unsigned int num);
        • void pois recebe um ponteiro genérico de memória;
        • num: parâmetro que recebe o tamanho do espaço de memória em bytes a ser alocado;
        • Caso der algum ERRO, retorna NULL;
    • Alocar 1000 bytes de memória livre:

      char *p;
      p = (char *) malloc(1000);
      
    • Alocar espaço para 50 inteiros:

      int *p;
      p = (int *) malloc(50 * sizeof( int ) );
      
  • Free()

    • Diferente das variáveis definidas durante a escrita do programa, as variáveis alocadas dinamicamente não são liberadas automaticamente pelo programa; Quando alocamos memória dinamicamente é necessário que nós a liberemos quando ela não for mais necessária; Para isto existe a função free() cujo protótipo é:
      • void free(void *p);
    • Assim, para liberar a memória, basta passar como parâmetro para a função free() o ponteiro que aponta para o início da memória a ser desalocada;
    • Como o programa sabe quantos bytes devem ser liberados?
      • Quando se aloca a memória, o programa guarda o número de bytes alocados numa "tabela de alocação" interna;
    • Outro ponto muito importante é que a linguagem C não possui um gerenciamento automático do heap. Diante disso, é dever do programador liberar toda memória alocada na aplicação, evitando o que é chamado memory leak.
  • Calloc() - contiguous allocation

    • A função calloc() também serve para alocar memória, mas possui um protótipo um pouco diferente:
      • void *calloc (unsigned int num, unsigned int size);
        • void pois recebe um ponteiro genérico de memória;
        • num: quantidade de posições a serem alocadas;
        • size: tamanho do tipo de dado alocado;
  • Realloc() - reallocate

    • A função realloc é usada para redimensionar um espaço alocado previamente com malloc ou calloc;
    • A função modifica o tamanho da memória previamente alocada e apontada por *ptr para aquele especificado por num;
    • O valor de num pode ser maior ou menor que o original;
      • void *realloc(void *prt, unsigned int num)
    • 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 para o novo bloco, e nenhuma informação é perdida;
    • Se *ptr for nulo, aloca num bytes e devolve um ponteiro (igual malloc);
    • Se num é zero, a memória apontada por *ptr é liberada (igual free);
    • Se não houver memória suficiente para a alocação, um ponteiro nulo é devolvido e o bloco original é deixado inalterado;
  • Verificação de Alocação: Sempre verifique se a alocação de memória foi bem-sucedida antes de usar um ponteiro alocado dinamicamente. Você pode verificar se o ponteiro é NULL após a alocação para garantir que a alocação ocorreu corretamente.

  • Gerenciamento de Memória: Ao usar alocação dinâmica de memória, lembre-se de que você é responsável por gerenciar a memória alocada. Certifique-se de liberar a memória quando não precisar mais dela para evitar vazamentos de memória.

  • A alocação dinâmica de memória é particularmente útil quando você precisa alocar memória para estruturas de dados cujo tamanho não é conhecido em tempo de compilação, como listas encadeadas, árvores e matrizes de tamanho variável. No entanto, o gerenciamento adequado da memória é essencial para evitar problemas como vazamentos de memória e corrupção de memória. Portanto, é importante entender e usar essas funções com cuidado.

  • Cabe ressaltar que em todas as funções de alocação dinâmica é necessário validar o endereço retornado, pois essas funções retornam NULL se a quantidade informada não pode ser alocada. Lembre-se, do primeiro artigo, que o valor NULL não impede erros de execução, porém fornece uma alternativa para testar se o ponteiro armazena um endereço válido. Em alguns compiladores é necessário realizar o cast do endereço retornado. No padrão ANSI não é necessário, ficando a critério do programador realizar o cast de forma explícita.

  • Independente do mecanismo utilizado num sistema de alocação dinâmica os ponteiros têm uma função especial. Por tratar-se de um mecanismo dinâmico, isto é, que varia conforme a execução do programa, os ponteiros oferecem os recursos necessários para manipular endereços de blocos de memória. Esse assunto torna-se importante para discutir um tópico conhecido como tipo de dados abstrato.

  • Exemplo