Ir para o conteúdo

CCT-UFCA/Ciência da Computação/Introdução à Programação/Alocação Dinâmica

De Wikiversidade

Alocação Dinâmica

[editar | editar código]

Visão geral

[editar | editar código]

A alocação dinâmica, em conjunto com ponteiros, é amplamente utilizada ao programar estruturas de dados. Na linguagem C, esse mecanismo serve para reservar memória em tempo de execução e é feito por meio de funções da biblioteca <stdlib.h>, como malloc(), calloc() e realloc(). Neste material, focaremos inicialmente apenas na função malloc(), mas recomendamos ao leitor consultar a documentação das demais funções para aprofundar o entendimento do tema.

Tipos de alocação

[editar | editar código]

A alocação de memória para estruturas de dados pode ocorrer de duas formas: estática e dinâmica. Embora a alocação estática seja bastante comum, a alocação dinâmica será o foco principal no desenvolvimento dos algoritmos em disciplinas como Estruturas de Dados. Ainda assim, é fundamental dominar ambas as abordagens para avaliar corretamente, em cada situação, qual delas deve ser utilizada.

Alocação Estática vs Dinâmica

[editar | editar código]

Na alocação estática, o espaço de memória é reservado em tempo de compilação, geralmente por meio de declarações como int v[100]; ou variáveis globais e locais cujos tamanhos são fixos. Esse tipo de alocação é simples, pois o compilador já conhece, antes da execução, quanto de memória será necessário. No entanto, as estruturas de tamanho estático não podem crescer ou encolher em tempo de execução; seu tamanho permanece imutável durante a vida do programa.

Já na alocação dinâmica, a memória é alocada em tempo de execução, por meio de funções como malloc(), calloc() e realloc(). Por exemplo, chamar malloc(100 * sizeof(int)); reserva, no heap, espaço suficiente para 100 inteiros. O programador passa a ter controle sobre quando e quanto de memória deseja alocar ou liberar (com free), permitindo a criação de estruturas como listas encadeadas, vetores redimensionáveis ou árvores dinâmicas, cujo tamanho pode ser definido em tempo de execução, conforme a necessidade.

Vantagens e desvantagens

[editar | editar código]
  • Estática:
    • Vantagens: simplicidade na declaração; desalocação automática ao sair do escopo; menor risco de vazamento de memória.
    • Desvantagens: tamanho fixo; desperdício potencial de memória se reservada demais; limitação quando não se conhece antecipadamente a quantidade exata de elementos.
  • Dinâmica:
    • Vantagens: flexibilidade para criar estruturas de tamanho variável; alocação somente quando necessário; pode aproveitar melhor a memória disponível; os dados alocados dinamicamente permanecem acessíveis após o término da função que os alocou, até que sejam liberados com free().
    • Desvantagens: mais complexa, pois exige chamadas explícitas a malloc() / calloc() / realloc() e free(); risco de vazamentos (não liberar memória) ou de uso de áreas já liberadas (dangling pointers); risco de fragmentação do heap.

Quando usar cada uma

[editar | editar código]
  • Use alocação estática quando o tamanho da estrutura for conhecido e constante em todas as execuções ou quando a simplicidade for o principal objetivo.
  • Use alocação dinâmica quando for necessário flexibilidade para crescer ou reduzir a estrutura em tempo de execução, ou quando não se sabe de antemão quantos elementos serão necessários.

Alocando Memória

[editar | editar código]

A alocação dinâmica permite que o programa reserve memória em tempo de execução, de acordo com a necessidade. Na linguagem C, a função mais comum para isso é o malloc() (memory allocation), presente na biblioteca padrão <stdlib.h>.

ponteiro = (tipo *) malloc(quantidade * sizeof(tipo));
  • tipo: indica o tipo de dado que o ponteiro irá armazenar.
  • quantidade: número de elementos.
  • sizeof(tipo): retorna o tamanho em bytes do tipo especificado.

Exemplo:

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

#define MAX_VET 10

int main () {
    int *vet = (int *) malloc(sizeof(int) * MAX_VET);
    
    if (vet == NULL) {
        printf("Erro na alocação de memória!\n");
        return 1;
    }
    
    for (int i = 0; i < MAX_VET; i++) {
        vet[i] = i;
    }
    
    for (int i = 0; i < MAX_VET; i++) {
       printf("%d, ", vet[i]);
    }
    
    puts("");
    
    return 0;
}

Esse exemplo consiste em:

  • malloc(sizeof(int) * MAX_VET): aloca espaço suficiente na memória para armazenar 10 inteiros.
  • (int *): converte, de forma explícita, o ponteiro genérico retornado por malloc() para um ponteiro do tipo int.
  • int *vet: declara a variável ponteiro que irá armazenar o endereço da memória alocada.

Após a alocação, o código preenche o vetor com uma sequência de números de 0 a 9. Em seguida, o segundo laço imprime esses valores na tela.

⚠️ Importante: Sempre verifique se o ponteiro retornado por malloc() é diferente de NULL antes de usar a memória, pois um retorno NULL indica falha na alocação. Observe o teste condicional presente na linha 9.

Liberando Memória Alocada

[editar | editar código]

Diferentemente da alocação estática, na alocação dinâmica o programador é responsável por liberar a memória que não será mais utilizada, evitando vazamentos de memória.

Para isso, utiliza-se a função free(), também da biblioteca <stdlib.h>.

Sintaxe:

free(ponteiro);

ponteiro: é o endereço da memória previamente alocada com malloc, calloc ou realloc. O exemplo anterior está correto, mas falta incluir a liberação da memória. Para finalizá-lo, vamos incluir a liberação da memória:

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

#define MAX_VET 10

int main () {
    int *vet = (int *) malloc(sizeof(int) * MAX_VET);
    
    if (vet == NULL) {
        printf("Erro na alocação de memória!\n");
        return 1;
    }
    
    for (int i = 0; i < MAX_VET; i++) {
        vet[i] = i;
    }
    
    for (int i = 0; i < MAX_VET; i++) {
       printf("%d, ", vet[i]);
    }
    
    puts("");
    
    // Libera a memória previamente alocada
    free(vet);
    
    return 0;
}
  • free(vet): libera a memória que foi previamente alocada com malloc(). Esse passo é essencial para evitar vazamentos de memória em programas C.