Gerenciamento de Memória Dinâmica

Memória dinâmica é como alugar um apartamento — você solicita quando precisa (malloc), e devolve quando termina (free). Se você nunca devolve, continua ocupando recursos — isso é um vazamento de memória.

Por Que Memória Dinâmica

Variáveis locais na pilha desaparecem quando uma função retorna, e tamanhos de array devem ser determinados em tempo de compilação. Quando o tamanho dos dados só é conhecido em tempo de execução, ou você precisa que os dados persistam entre chamadas de função, você precisa de memória dinâmica no heap.

C
int n;
scanf("%d", &n);
int arr[n];
⚠️ Atenção: Arrays de tamanho variável (VLA) são um recurso do C99 não suportado por todos os compiladores, e não são recomendados para arrays grandes. A abordagem correta é usar malloc.

malloc e free

malloc aloca um número especificado de bytes no heap e retorna void *. Você deve fazer free da memória quando terminar.

C
int *p = (int *)malloc(sizeof(int) * 5);
if (p == NULL) {
    printf("Falha na alocação de memória\n");
    return 1;
}
for (int i = 0; i < 5; i++) {
    p[i] = i * 10;
}
for (int i = 0; i < 5; i++) {
    printf("%d ", p[i]);
}
free(p);
p = NULL;
TEXT
0 10 20 30 40
💡 Dica: malloc não inicializa a memória — o conteúdo são valores lixo. Sempre verifique o valor de retorno quanto a NULL após a alocação. Definir o ponteiro como NULL após free é uma boa prática para evitar ponteiros pendentes.

calloc

calloc aloca memória e inicializa cada bit com 0. Seus parâmetros são o número de elementos e o tamanho de cada elemento.

C
int *p = (int *)calloc(5, sizeof(int));
if (p) {
    for (int i = 0; i < 5; i++) {
        printf("%d ", p[i]);
    }
    free(p);
}
TEXT
0 0 0 0 0

malloc vs calloc:

Função Inicialização Parâmetros
malloc Sem inicialização Total de bytes
calloc Zerado Quantidade de elementos, tamanho de cada elemento

realloc

realloc redimensiona memória previamente alocada — pode expandir ou reduzir.

C
int *p = (int *)malloc(sizeof(int) * 3);
p[0] = 10; p[1] = 20; p[2] = 30;

int *tmp = (int *)realloc(p, sizeof(int) * 5);
if (tmp) {
    p = tmp;
    p[3] = 40;
    p[4] = 50;
    for (int i = 0; i < 5; i++) {
        printf("%d ", p[i]);
    }
    free(p);
}
TEXT
10 20 30 40 50
⚠️ Atenção: realloc pode retornar um novo endereço (os dados originais são copiados automaticamente) ou o mesmo endereço. Nunca escreva p = realloc(p, ...) — se falhar e retornar NULL, o ponteiro original é perdido. Use uma variável temporária para receber o resultado.

Array 1D Dinâmico

Exemplo

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

int main(void) {
    int n;
    printf("Digite a quantidade de elementos: ");
    scanf("%d", &n);

    int *arr = (int *)calloc(n, sizeof(int));
    if (!arr) return 1;

    for (int i = 0; i < n; i++) {
        arr[i] = (i + 1) * (i + 1);
    }

    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");

    free(arr);
    arr = NULL;
    return 0;
}
▶ Experimente
TEXT
Digite a quantidade de elementos: 5
1 4 9 16 25

Array 2D Dinâmico

Existem duas abordagens comuns para implementar arrays 2D dinâmicos.

Abordagem 1: Array de Ponteiros (Cada Linha Alocada Independentemente)

C
int rows = 3, cols = 4;
int matrix = (int )malloc(sizeof(int *) * rows);
for (int i = 0; i < rows; i++) {
    matrix[i] = (int *)calloc(cols, sizeof(int));
}

matrix[1][2] = 99;
printf("%d\n", matrix[1][2]);

for (int i = 0; i < rows; i++) {
    free(matrix[i]);
}
free(matrix);
TEXT
99

Abordagem 2: Memória Contígua (Única Alocação)

C
int rows = 3, cols = 4;
int *buf = (int *)calloc(rows * cols, sizeof(int));
int matrix = (int )malloc(sizeof(int *) * rows);
for (int i = 0; i < rows; i++) {
    matrix[i] = buf + i * cols;
}

matrix[2][3] = 77;
printf("%d\n", matrix[2][3]);

free(matrix);
free(buf);
TEXT
77
💡 Dica: A Abordagem 2 tem memória contígua, o que é amigável ao cache e mais simples de liberar. A Abordagem 1 permite que cada linha tenha um número diferente de colunas (array denteado).

Exemplo

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

int main(void) {
    int rows = 3, cols = 4;
    int m = (int )malloc(sizeof(int *) * rows);
    int *buf = (int *)calloc(rows * cols, sizeof(int));
    for (int i = 0; i < rows; i++) {
        m[i] = buf + i * cols;
    }

    int val = 1;
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            m[i][j] = val++;
        }
    }

    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            printf("%3d", m[i][j]);
        }
        printf("\n");
    }

    free(m);
    free(buf);
    return 0;
}
▶ Experimente
TEXT
  1  2  3  4
  5  6  7  8
  9 10 11 12

Erros Comuns de Memória Dinâmica

Esquecer o free — Vazamento de Memória

C
void leak(void) {
    int *p = (int *)malloc(sizeof(int) * 100);
}

Quando a função termina, a variável local p desaparece, mas os 100 ints de memória do heap ainda estão ocupados e nunca podem ser liberados.

Double free

C
int *p = (int *)malloc(sizeof(int));
free(p);
free(p);
⚠️ Atenção: Chamar free na mesma memória duas vezes é comportamento indefinido e pode causar crash no programa. Defina o ponteiro como NULL imediatamente após free — chamar free(NULL) é seguro.

Usar Memória Liberada

C
int *p = (int *)malloc(sizeof(int));
*p = 42;
free(p);
printf("%d\n", *p);
⚠️ Atenção: Acessar memória após liberá-la é um ponteiro pendente — os resultados são imprevisíveis.

Acesso Fora dos Limites

C
int *p = (int *)malloc(sizeof(int) * 5);
p[5] = 100;

Foram alocados 5 elementos com índices de 0 a 4, então p[5] está fora dos limites.

Introdução ao valgrind

Valgrind é uma ferramenta de verificação de memória no Linux que pode detectar vazamentos de memória, acessos fora dos limites e leituras não inicializadas.

BASH
gcc -g -o myapp myapp.c
valgrind --leak-check=full ./myapp

Saída típica:

TEXT
==12345== HEAP SUMMARY:
==12345==     in use at exit: 400 bytes in 1 blocks
==12345==   total heap usage: 2 allocs, 1 frees, 800 bytes allocated
==12345==
==12345== 400 bytes in 1 blocks are definitely lost in loss record 1 of 1
==12345==    at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==12345==    by 0x10915E: leak (myapp.c:5)
💡 Dica: No Windows você pode usar Dr. Memory ou o debug heap do Visual Studio. No macOS, use a ferramenta Leaks do Instruments.

❓ Perguntas Frequentes

P: Quais valores estão na memória retornada por malloc? R: Indefinidos — são dados lixo de usos anteriores. Use calloc se precisar de memória zerada, ou chame memset manualmente.

P: Um ponteiro ainda tem valor após free? R: free apenas libera a memória — não muda o valor do ponteiro. Ele ainda contém aquele endereço (ponteiro pendente). Por isso você deve defini-lo como NULL imediatamente após liberar.

P: realloc perde os dados originais ao expandir? R: Não. realloc garante que os dados originais são preservados (pelo menos min(tamanho_antigo, tamanho_novo) bytes). Se mudar para um novo endereço, copia automaticamente.

P: Posso fazer free de uma variável na pilha? R: Absolutamente não. free só pode liberar memória do heap retornada por malloc/calloc/realloc. Chamar free em um endereço na pilha é comportamento indefinido.

📖 Resumo

📝 Exercícios

  1. Escreva um programa: o usuário digita n, aloca dinamicamente n ints, lê n números, ordena e exibe, depois libera a memória
  2. Escreva funções de array 2D dinâmico: int create_matrix(int rows, int cols) e void free_matrix(int m, int rows). Em main, crie uma matriz 4x5, atribua valores e imprima
  3. Crie intencionalmente um vazamento de memória no seu código, depois use valgrind (ou Dr. Memory) para detectá-lo e examine a saída
100%