A alocação dinâmica de memória é uma das características mais poderosas da linguagem C, permitindo que programas solicitem memória durante a execução conforme a necessidade. Diferentemente das variáveis estáticas ou automáticas, cujo tamanho é conhecido em tempo de compilação, a alocação dinâmica possibilita criar estruturas de dados flexíveis, como listas encadeadas, árvores e vetores de tamanho variável. As funções principais para gerenciar essa memória são malloc (memory allocation) e free (liberação). Nesta aula, abordaremos em detalhes como usar malloc com sizeof, a importância de free, a verificação de ponteiros nulos e como evitar vazamentos de memória.

malloc e sizeof

A função malloc aloca um bloco de memória contíguo de um tamanho especificado em bytes e retorna um ponteiro para o início desse bloco. O protótipo é void *malloc(size_t size);. O tipo size_t é um tipo inteiro sem sinal definido em <stddef.h> (ou <stdlib.h>). O valor retornado é um ponteiro genérico void*, que deve ser convertido para o tipo de ponteiro desejado. É fundamental usar sizeof para calcular o tamanho correto, garantindo portabilidade e evitando erros manuais.

Exemplo: alocar um vetor de 10 inteiros. sizeof(int) retorna o número de bytes que um int ocupa na plataforma (geralmente 4). Assim, malloc(10 * sizeof(int)) aloca espaço para 10 inteiros. O ponteiro retornado deve ser verificado contra NULL antes do uso.

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

int main() {
    int *vetor;
    int n = 10;

    vetor = (int *)malloc(n * sizeof(int));
    if (vetor == NULL) {
        fprintf(stderr, "Erro: memória insuficiente\n");
        return 1;
    }

    // Uso do vetor
    for (int i = 0; i < n; i++) {
        vetor[i] = i * 2;
    }

    // Liberação
    free(vetor);
    return 0;
}

free

A função free libera a memória previamente alocada por malloc, calloc ou realloc. Seu protótipo é void free(void *ptr);. Após chamar free, o ponteiro se torna inválido (dangling pointer) e não deve ser dereferenciado. É boa prática atribuir NULL ao ponteiro após liberar para evitar uso acidental.

Chamar free duas vezes no mesmo ponteiro (double free) é um erro grave que pode corromper o gerenciador de memória. Também não se deve liberar memória que não foi alocada dinamicamente (como variáveis locais).

int *p = (int *)malloc(sizeof(int));
if (p != NULL) {
    *p = 42;
    printf("%d\n", *p);
    free(p);
    p = NULL; // evita dangling pointer
}

Verificando NULL

malloc retorna NULL quando não consegue alocar a memória solicitada (por exemplo, falta de espaço). Ignorar essa verificação pode levar a dereferenciamento de ponteiro nulo, causando falhas de segmentação. Portanto, sempre verifique se o retorno é NULL antes de usar o ponteiro.

Além disso, é importante notar que malloc(0) pode retornar NULL ou um ponteiro único que não deve ser dereferenciado. A norma permite ambos os comportamentos, então evite alocar zero bytes.

int *p = (int *)malloc(100 * sizeof(int));
if (p == NULL) {
    // Tratamento de erro: encerrar ou tentar recuperação
    exit(EXIT_FAILURE);
}
// ... uso ...
free(p);

Vazamentos de memória

Um vazamento de memória (memory leak) ocorre quando a memória alocada dinamicamente não é liberada após o uso, fazendo com que o programa consuma cada vez mais memória. Em programas longos ou sistemas embarcados, isso pode esgotar a memória disponível. Para evitar vazamentos, cada malloc deve ter um free correspondente, e é importante liberar toda a memória antes de o programa terminar.

Ferramentas como Valgrind (Linux) ou Dr. Memory (Windows) ajudam a detectar vazamentos. Boas práticas incluem: sempre parear alocação e liberação; usar ponteiros auxiliares para não perder a referência; e liberar estruturas complexas percorrendo todos os nós.

// Exemplo de vazamento: a memória alocada para 'buffer' nunca é liberada
void func() {
    char *buffer = (char *)malloc(256);
    // ... uso ...
    // Esqueceu de free(buffer);
}

// Corrigido:
void func() {
    char *buffer = (char *)malloc(256);
    if (buffer != NULL) {
        // ... uso ...
        free(buffer);
    }
}

Boas práticas

Sempre use sizeof com o tipo correto; evite números mágicos. Prefira p = malloc(sizeof(*p)) para alocar um único elemento, pois se o tipo de p mudar, a alocação se ajusta automaticamente. Nunca dereferencie um ponteiro após free. Em programas grandes, considere criar funções auxiliares para alocar e liberar memória de estruturas complexas, centralizando a lógica.

Referências

Exercícios

  1. Escreva um programa que aloque dinamicamente um vetor de 20 floats, preencha com valores de 1.0 a 20.0 e depois libere a memória. Inclua verificação de NULL.
  2. ✓ Resposta:
    #include <stdio.h>
    #include <stdlib.h>
    
    int main() {
        float *vetor;
        int n = 20;
    
        vetor = (float *)malloc(n * sizeof(float));
        if (vetor == NULL) {
            fprintf(stderr, "Erro de alocação\n");
            return 1;
        }
    
        for (int i = 0; i < n; i++) {
            vetor[i] = (float)(i + 1);
        }
    
        free(vetor);
        return 0;
    }
    
  3. Crie uma função que aloque uma matriz de inteiros de dimensões m x n (passadas como parâmetros) e retorne o ponteiro para a matriz (use um único bloco de memória). A função deve retornar NULL em caso de falha.
  4. ✓ Resposta:
    #include <stdlib.h>
    
    int *cria_matriz(int m, int n) {
        int *matriz = (int *)malloc(m * n * sizeof(int));
        return matriz; // pode ser NULL
    }
    
  5. O que acontece se você chamar free duas vezes no mesmo ponteiro? Escreva um pequeno programa para demonstrar (não execute em produção).
  6. ✓ Resposta:

    Double free causa comportamento indefinido, geralmente corrompendo o heap e causando falha de segmentação. Exemplo:

    #include <stdlib.h>
    int main() {
        int *p = malloc(sizeof(int));
        free(p);
        free(p); // undefined behavior
        return 0;
    }
    
  7. Explique por que é importante usar sizeof em malloc, em vez de um número fixo como 4 para int.
  8. ✓ Resposta:

    O tamanho de tipos como int pode variar entre plataformas (ex.: 2 bytes em alguns sistemas embarcados, 4 bytes em PCs). Usar sizeof garante portabilidade e evita erros. Além disso, se o tipo mudar (ex.: de int para long), o sizeof se ajusta automaticamente, mantendo o código correto.

  9. Escreva um programa que aloque uma string de 100 caracteres, leia uma linha do usuário com fgets, e depois libere a memória. Inclua verificação de NULL.
  10. ✓ Resposta:
    #include <stdio.h>
    #include <stdlib.h>
    
    int main() {
        char *str = (char *)malloc(100 * sizeof(char));
        if (str == NULL) {
            fprintf(stderr, "Erro de alocação\n");
            return 1;
        }
    
        printf("Digite uma linha: ");
        if (fgets(str, 100, stdin) != NULL) {
            printf("Você digitou: %s", str);
        }
    
        free(str);
        return 0;
    }