Aula 22 — Alocação Dinâmica de Memória

malloc · calloc · realloc · free — gerenciando memória em tempo de execução

1. Por que Alocar Memória Dinamicamente?

Até agora, todos os arrays que você declarou tinham tamanho fixo em tempo de compilação:

int notas[30];          // sempre 30 — mesmo se só houver 3 alunos
float precos[1000];     // sempre 1000 — mesmo se forem 5 produtos

Esse modelo tem dois problemas sérios: ou você desperdiça memória (declarou 1000, usa 5), ou fica sem espaço (declarou 30, mas vieram 31 alunos). A alocação dinâmica resolve os dois.

Alocação dinâmica é a capacidade de reservar blocos de memória durante a execução do programa, na quantidade exata necessária — que pode depender de entradas do usuário, do tamanho de um arquivo, ou de qualquer cálculo em tempo real. Em C, isso é feito com as funções malloc, calloc e realloc da biblioteca <stdlib.h>.

Memória estática vs. dinâmica

Característica Estática (stack) Dinâmica (heap)
Tamanho definido em Tempo de compilação Tempo de execução
Gerenciamento Automático — criada e destruída com a função Manual — você aloca e você libera
Capacidade Limitada (stack típica: 1–8 MB) Limitada apenas pela RAM disponível
Flexibilidade Baixa — tamanho fixo Alta — pode crescer e encolher
Risco Stack overflow (recursão profunda) Memory leak se não liberar com free
Incluir <stdlib.h> é obrigatório. As funções malloc, calloc, realloc e free são declaradas nesse cabeçalho. Sem ele, o compilador não conhece os protótipos e pode gerar erros ou avisos.

2. malloc — Alocar um Bloco de Memória

malloc(size) (memory allocation) aloca um bloco contíguo de size bytes no heap e retorna um ponteiro void * para o início do bloco. O conteúdo não é inicializado — pode conter qualquer valor. Retorna NULL se a alocação falhar.

Sintaxe

void *malloc(size_t size);

/* Uso típico: alocar n elementos de um tipo */
tipo *p = (tipo *) malloc(n * sizeof(tipo));
Por que usar sizeof(tipo)? O tamanho de cada tipo pode variar entre compiladores e plataformas. sizeof(int) garante o tamanho correto independente do sistema. Nunca escreva valores fixos como malloc(n * 4) — use sempre malloc(n * sizeof(int)).

Exemplo 1 — vetor de tamanho definido pelo usuário

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

int main() {
    int n;
    printf("Quantos alunos? ");
    scanf("%d", &n);

    /* Aloca exatamente n inteiros no heap */
    int *notas = (int *) malloc(n * sizeof(int));

    /* SEMPRE verifique se a alocação teve sucesso */
    if (notas == NULL) {
        printf("Erro: memória insuficiente!\n");
        return 1;
    }

    /* Usa exatamente como um array normal */
    for (int i = 0; i < n; i++) {
        printf("Nota do aluno %d: ", i + 1);
        scanf("%d", &notas[i]);
    }

    int soma = 0;
    for (int i = 0; i < n; i++)
        soma += notas[i];

    printf("Média: %.1f\n", (float)soma / n);

    /* Libera a memória — obrigatório! */
    free(notas);
    notas = NULL;   // boa prática: evita dangling pointer

    return 0;
}
Quantos alunos? 3
Nota do aluno 1: 8
Nota do aluno 2: 7
Nota do aluno 3: 9
Média: 8.0

O que acontece na memória

Região Conteúdo Endereço (exemplo)
Stack Variável notas (o ponteiro) 0x7fff...10
Heap Bloco de n × 4 bytes (os dados) 0x5555...a0

O ponteiro notas na stack aponta para o bloco de dados no heap. Após free(notas), o bloco no heap é devolvido ao sistema — mas o ponteiro ainda contém o endereço antigo até você atribuir NULL.

3. calloc — Alocar e Zerar

calloc(quantidade, tamanho) (contiguous allocation) aloca espaço para quantidade elementos de tamanho bytes cada, e inicializa todos os bytes com zero. É equivalente a malloc seguido de memset(..., 0, ...), mas geralmente mais eficiente.

Sintaxe

void *calloc(size_t quantidade, size_t tamanho);

/* Uso típico */
int *v = (int *) calloc(n, sizeof(int));   // todos os elementos começam em 0

Comparação direta: malloc vs. calloc

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

int main() {
    int n = 5;

    /* malloc: conteúdo indefinido (lixo) */
    int *vm = (int *) malloc(n * sizeof(int));
    printf("malloc (sem init): ");
    for (int i = 0; i < n; i++)
        printf("%d ", vm[i]);   // valores imprevisíveis!
    printf("\n");

    /* calloc: todos zeros garantidos */
    int *vc = (int *) calloc(n, sizeof(int));
    printf("calloc (zeros):    ");
    for (int i = 0; i < n; i++)
        printf("%d ", vc[i]);   // sempre 0 0 0 0 0
    printf("\n");

    free(vm);
    free(vc);
    return 0;
}
malloc (sem init): 0 0 134513 0 0 ← valores imprevisíveis
calloc (zeros): 0 0 0 0 0

Quando usar cada um

Use malloc quando… Use calloc quando…
Você vai preencher todos os elementos imediatamente Os elementos precisam começar em zero (contadores, flags, matrizes)
Você lê o array com scanf logo após alocar Parte do array pode ficar sem inicialização explícita
Desempenho é crítico e zeros não são necessários Você quer garantia de estado inicial limpo

Exemplo: tabela de frequência com calloc

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

int main() {
    /* Frequência de cada letra (a-z) — começa tudo em 0 */
    int *freq = (int *) calloc(26, sizeof(int));
    if (freq == NULL) return 1;

    char frase[] = "alocacao dinamica em c";

    for (int i = 0; frase[i] != '\0'; i++) {
        if (frase[i] >= 'a' && frase[i] <= 'z')
            freq[frase[i] - 'a']++;    // índice 0='a', 1='b', ...
    }

    printf("Frequência de letras em \"%s\":\n", frase);
    for (int i = 0; i < 26; i++) {
        if (freq[i] > 0)
            printf("  '%c': %d\n", 'a' + i, freq[i]);
    }

    free(freq);
    return 0;
}
Frequência de letras em "alocacao dinamica em c":
  'a': 5
  'c': 3
  'd': 1
  'e': 1
  'i': 2
  'l': 1
  'm': 2
  'n': 1
  'o': 2

4. realloc — Redimensionar um Bloco

realloc(ptr, novo_tamanho) redimensiona um bloco previamente alocado com malloc ou calloc. Se possível, expande o bloco no mesmo lugar; caso contrário, aloca um novo bloco, copia os dados antigos e libera o bloco original. Retorna NULL se falhar — sem liberar o bloco original.

Sintaxe

void *realloc(void *ptr, size_t novo_tamanho);
⚠️ Armadilha clássica do realloc: Se você escrever p = realloc(p, ...) e a alocação falhar, realloc retorna NULL sem liberar o bloco original. Ao atribuir NULL a p, você perde a referência ao bloco original — causando um memory leak. Sempre use uma variável temporária:
/* ❌ ERRADO — memory leak se realloc falhar */
p = realloc(p, novo_tamanho);

/* ✅ CORRETO — preserva o ponteiro original em caso de falha */
int *temp = realloc(p, novo_tamanho);
if (temp == NULL) {
    printf("Erro de alocação!\n");
    free(p);   // libera o bloco original antes de sair
    return 1;
}
p = temp;

Exemplo: lista que cresce dinamicamente

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

int main() {
    int capacidade = 4;       // tamanho inicial
    int tamanho    = 0;       // elementos realmente usados

    int *lista = (int *) malloc(capacidade * sizeof(int));
    if (lista == NULL) return 1;

    int valor;
    printf("Digite inteiros (0 para parar):\n");

    while (1) {
        printf("  > ");
        scanf("%d", &valor);
        if (valor == 0) break;

        /* Se cheio, dobra a capacidade */
        if (tamanho == capacidade) {
            capacidade *= 2;
            int *temp = (int *) realloc(lista, capacidade * sizeof(int));
            if (temp == NULL) {
                printf("Sem memória!\n");
                free(lista);
                return 1;
            }
            lista = temp;
            printf("  [capacidade expandida para %d]\n", capacidade);
        }

        lista[tamanho++] = valor;
    }

    printf("\nLista (%d elementos): ", tamanho);
    for (int i = 0; i < tamanho; i++)
        printf("%d ", lista[i]);
    printf("\n");

    free(lista);
    return 0;
}
Digite inteiros (0 para parar):
  > 10
  > 20
  > 30
  > 40
  > 50
  [capacidade expandida para 8]
  > 60
  > 0

Lista (6 elementos): 10 20 30 40 50 60
Estratégia de dobrar a capacidade é usada pelo ArrayList do Java e pela maioria das estruturas dinâmicas de alto desempenho. Ao dobrar (em vez de somar 1), o custo amortizado de cada inserção permanece O(1) — o número de realocações cresce apenas logaritmicamente com o total de elementos.

5. free — Liberar Memória

free(ptr) devolve ao sistema o bloco de memória previamente alocado com malloc, calloc ou realloc. Após o free, o bloco não pertence mais ao programa — acessá-lo é comportamento indefinido.
#include <stdlib.h>

int *p = (int *) malloc(10 * sizeof(int));
/* ... usa p ... */
free(p);       // devolve os 40 bytes ao sistema
p = NULL;      // evita dangling pointer

Regras fundamentais do free

Regra O que acontece se violar
Só libere ponteiros obtidos de malloc/calloc/realloc Comportamento indefinido — corrupção do heap
Não chame free duas vezes no mesmo ponteiro (double free) Comportamento indefinido — pode travar ou comprometer a segurança
Não use o ponteiro após free (use after free) Comportamento indefinido — pode acessar dados de outra variável
free(NULL) é seguro e não faz nada — (operação segura por definição)

Memory leak — o erro mais silencioso

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

/* ❌ ERRADO — cada chamada vaza memória */
void processarDados_errado(int n) {
    int *buf = (int *) malloc(n * sizeof(int));
    /* ... processa ... */
    return;   // buf não foi liberado! memória perdida para sempre
}

/* ✅ CORRETO — libera antes de retornar */
void processarDados_correto(int n) {
    int *buf = (int *) malloc(n * sizeof(int));
    if (buf == NULL) return;
    /* ... processa ... */
    free(buf);   // libera antes de sair da função
    buf = NULL;
}
⚠️ Memory leaks em programas longos são sérios. Um vazamento de 1 KB por chamada parece inofensivo, mas em um servidor que processa 1 milhão de requisições/dia, isso representa quase 1 GB de memória perdida por dia. Ferramentas como Valgrind (Linux) detectam vazamentos automaticamente.

6. Matrizes Dinâmicas

Matrizes bidimensionais também podem ser alocadas dinamicamente. Há duas abordagens principais:

Abordagem 1 — Array único (mais eficiente)

Aloca todos os elementos em um único bloco contíguo e usa aritmética de índice para simular linhas e colunas:

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

int main() {
    int linhas = 3, colunas = 4;

    /* Bloco único de linhas × colunas elementos */
    int *matriz = (int *) malloc(linhas * colunas * sizeof(int));
    if (matriz == NULL) return 1;

    /* Preenche com linha × coluna */
    for (int i = 0; i < linhas; i++)
        for (int j = 0; j < colunas; j++)
            matriz[i * colunas + j] = i * 10 + j;   // fórmula do índice

    /* Imprime */
    printf("Matriz %dx%d:\n", linhas, colunas);
    for (int i = 0; i < linhas; i++) {
        for (int j = 0; j < colunas; j++)
            printf("%4d", matriz[i * colunas + j]);
        printf("\n");
    }

    free(matriz);    // um único free — simples!
    return 0;
}
Matriz 3x4:
   0   1   2   3
  10  11  12  13
  20  21  22  23

Abordagem 2 — Array de ponteiros (linhas de tamanhos diferentes)

Permite linhas com tamanhos diferentes (jagged array) — muito útil para triângulos, grafos e matrizes esparsas:

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

int main() {
    int linhas = 3;
    int cols[] = {2, 4, 3};   // cada linha tem tamanho diferente

    /* Aloca array de ponteiros (uma linha cada) */
    int **m = (int **) malloc(linhas * sizeof(int *));
    if (m == NULL) return 1;

    /* Aloca cada linha individualmente */
    for (int i = 0; i < linhas; i++) {
        m[i] = (int *) malloc(cols[i] * sizeof(int));
        if (m[i] == NULL) { /* tratar erro */ return 1; }
        for (int j = 0; j < cols[i]; j++)
            m[i][j] = (i + 1) * 10 + j;
    }

    /* Imprime */
    for (int i = 0; i < linhas; i++) {
        printf("Linha %d (%d elem): ", i, cols[i]);
        for (int j = 0; j < cols[i]; j++)
            printf("%d ", m[i][j]);
        printf("\n");
    }

    /* Libera: cada linha e depois o array de ponteiros */
    for (int i = 0; i < linhas; i++)
        free(m[i]);
    free(m);

    return 0;
}
Linha 0 (2 elem): 10 11
Linha 1 (4 elem): 20 21 22 23
Linha 2 (3 elem): 30 31 32

7. Padrão Completo: Alocar, Usar, Verificar, Liberar

Um programa robusto que usa alocação dinâmica segue sempre este padrão:

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

typedef struct {
    char  nome[50];
    float salario;
    int   anos;
} Funcionario;

int main() {
    int n;
    printf("Número de funcionários: ");
    scanf("%d", &n);

    /* 1. ALOCAR — e verificar imediatamente */
    Funcionario *equipe = (Funcionario *) malloc(n * sizeof(Funcionario));
    if (equipe == NULL) {
        fprintf(stderr, "Erro: memória insuficiente para %d funcionários.\n", n);
        return 1;
    }

    /* 2. USAR */
    for (int i = 0; i < n; i++) {
        printf("\n--- Funcionário %d ---\n", i + 1);
        printf("Nome:    "); scanf(" %49[^\n]", equipe[i].nome);
        printf("Salário: "); scanf("%f",         &equipe[i].salario);
        printf("Anos:    "); scanf("%d",          &equipe[i].anos);
    }

    /* 3. PROCESSAR */
    float total = 0;
    int   senioridade = 0;
    for (int i = 0; i < n; i++) {
        total += equipe[i].salario;
        if (equipe[i].anos >= 5) senioridade++;
    }

    printf("\n=== Resumo da Equipe ===\n");
    printf("Total de funcionários: %d\n", n);
    printf("Folha total:           R$%.2f\n", total);
    printf("Salário médio:         R$%.2f\n", total / n);
    printf("Seniores (≥5 anos):    %d\n", senioridade);

    /* 4. LIBERAR — sempre ao final */
    free(equipe);
    equipe = NULL;

    return 0;
}
Número de funcionários: 2

--- Funcionário 1 ---
Nome: Ana Oliveira
Salário: 5500
Anos: 7

--- Funcionário 2 ---
Nome: Carlos Mota
Salário: 4200
Anos: 2

=== Resumo da Equipe ===
Total de funcionários: 2
Folha total: R$9700.00
Salário médio: R$4850.00
Seniores (≥5 anos): 1

8. Resumo das Funções de Alocação

Função Assinatura Inicializa? Parâmetros Uso principal
malloc void *malloc(size_t n) ❌ Lixo Total de bytes Alocação rápida quando vai preencher imediatamente
calloc void *calloc(size_t qtd, size_t tam) ✅ Zeros Quantidade e tamanho de cada elemento Arrays de contadores, matrizes, flags
realloc void *realloc(void *p, size_t n) — (preserva dados) Ponteiro existente e novo tamanho total Crescimento ou encolhimento dinâmico
free void free(void *p) — Ponteiro a liberar Obrigatório após cada malloc/calloc

Checklist de boas práticas

✅ Sempre faça:
❌ Nunca faça:

9. C vs. Java — Gerenciamento de Memória

Aspecto C Java
Alocação Manual — malloc, calloc, realloc Automática — new
Liberação Manual — free obrigatório Automática — Garbage Collector
Memory leak ⚠️ Possível e silencioso ✅ Improvável — GC libera memória inacessível
Dangling pointer ⚠️ Possível — use after free ✅ Impossível — referência nula gera exceção
Controle sobre desempenho ✅ Total — você decide quando alocar e liberar ⚠️ Parcial — GC pode pausar o programa inesperadamente
Array dinâmico equivalente realloc manual ArrayList<Integer> automático

Side-by-side: array de tamanho dinâmico

C — manual e explícito
int n = lerTamanho();
int *v = malloc(n * sizeof(int));
if (!v) { /* erro */ }

// ... usa v[0]..v[n-1] ...

free(v);   // obrigatório!
Java — automático e seguro
int n = lerTamanho();
int[] v = new int[n];

// ... usa v[0]..v[n-1] ...

// GC libera quando v sair de
// escopo — não há free

Quiz — Aula 22

1. Qual é a diferença fundamental entre malloc e calloc?

2. O que malloc retorna quando a alocação de memória falha?

3. Por que é importante usar sizeof(int) em vez do valor fixo 4 ao chamar malloc?

4. Qual é o erro no código abaixo?

int *p = (int *) malloc(10 * sizeof(int));
p = realloc(p, 20 * sizeof(int));

5. Qual das afirmações sobre free é verdadeira?

6. Qual é a vantagem de usar a estratégia de dobrar a capacidade no realloc em vez de aumentar 1 elemento por vez?

7. Qual é a fórmula correta para acessar o elemento na linha i, coluna j de uma matriz alocada como array único com C colunas?

8. Em Java, o programador não precisa chamar nenhum equivalente a free. Por que isso é possível?