Até agora, trabalhamos com arrays de tamanho fixo, definidos em tempo de compilação. Em muitos problemas reais, porém, o tamanho dos dados só é conhecido em tempo de execução, ou precisa crescer e encolher dinamicamente. Para isso, a linguagem C oferece funções de alocação dinâmica de memória (malloc, calloc, realloc) que permitem criar arrays na heap, gerenciando a memória manualmente. Nesta aula, vamos explorar como alocar, redimensionar e liberar arrays dinâmicos, incluindo matrizes (arrays de arrays).

Alocando arrays na heap

Em C, arrays estáticos são alocados na pilha (stack) e têm tamanho fixo. Para criar um array cujo tamanho é definido em tempo de execução, usamos a função malloc (memory allocation), que reserva um bloco de bytes na heap e retorna um ponteiro para o início desse bloco. A sintaxe típica é:

int *arr = (int*) malloc(n * sizeof(int));

Aqui, n é o número de elementos desejado. sizeof(int) dá o tamanho de cada elemento em bytes. O cast (int*) é opcional em C, mas recomendado para clareza (em C++ é obrigatório). Se a alocação falhar (por falta de memória), malloc retorna NULL. Sempre verifique o retorno!

Outra função útil é calloc, que aloca e inicializa todos os bytes com zero:

int *arr = (int*) calloc(n, sizeof(int));

Exemplo prático de alocação de um array de inteiros:

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

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

    int *arr = (int*) malloc(n * sizeof(int));
    if (arr == NULL) {
        fprintf(stderr, "Erro de alocação\n");
        return 1;
    }

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

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

    free(arr);
    return 0;
}

Redimensionando

Muitas vezes precisamos aumentar ou diminuir o tamanho de um array dinâmico. A função realloc (reallocation) permite redimensionar um bloco de memória previamente alocado. Ela recebe o ponteiro original e o novo tamanho em bytes, e retorna um ponteiro para o bloco redimensionado (que pode estar em um novo endereço).

int *arr = (int*) malloc(5 * sizeof(int));
// ... usar arr ...
arr = (int*) realloc(arr, 10 * sizeof(int)); // aumenta para 10 elementos

Cuidado: realloc pode mover o bloco para outra posição, copiando o conteúdo antigo. O ponteiro original não deve mais ser usado; sempre atribua o retorno de realloc ao mesmo ponteiro (ou a um temporário para evitar vazamento se falhar). Exemplo seguro:

int *temp = (int*) realloc(arr, novo_tamanho * sizeof(int));
if (temp == NULL) {
    // erro: arr ainda existe, mas não foi redimensionado
    fprintf(stderr, "Falha ao redimensionar\n");
} else {
    arr = temp;
}

Exemplo completo de redimensionamento dinâmico (como um vetor que cresce conforme necessário):

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

int main() {
    int capacidade = 2;
    int *arr = (int*) malloc(capacidade * sizeof(int));
    int count = 0;
    int valor;

    printf("Digite números (negativo para parar):\n");
    while (1) {
        scanf("%d", &valor);
        if (valor < 0) break;

        if (count == capacidade) {
            capacidade *= 2;
            int *temp = (int*) realloc(arr, capacidade * sizeof(int));
            if (temp == NULL) {
                fprintf(stderr, "Erro ao realocar\n");
                free(arr);
                return 1;
            }
            arr = temp;
        }
        arr[count++] = valor;
    }

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

    free(arr);
    return 0;
}

Matrizes dinâmicas

Uma matriz dinâmica é um array de ponteiros, onde cada ponteiro aponta para um array (linha). Podemos alocar uma matriz de linhas x colunas da seguinte forma:

int **mat = (int**) malloc(linhas * sizeof(int*));
for (int i = 0; i < linhas; i++) {
    mat[i] = (int*) malloc(colunas * sizeof(int));
}

Isso cria uma matriz onde cada linha pode ter tamanho diferente (matriz irregular ou jagged array). Para acessar o elemento [i][j], usamos mat[i][j] normalmente.

Exemplo completo de criação e preenchimento de uma matriz 3x4:

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

int main() {
    int linhas = 3, colunas = 4;
    int **mat = (int**) malloc(linhas * sizeof(int*));
    for (int i = 0; i < linhas; i++) {
        mat[i] = (int*) malloc(colunas * sizeof(int));
    }

    // Preenche com valores i+j
    for (int i = 0; i < linhas; i++) {
        for (int j = 0; j < colunas; j++) {
            mat[i][j] = i + j;
        }
    }

    // Imprime
    for (int i = 0; i < linhas; i++) {
        for (int j = 0; j < colunas; j++) {
            printf("%d ", mat[i][j]);
        }
        printf("\n");
    }

    // Libera memória (importante!)
    for (int i = 0; i < linhas; i++) {
        free(mat[i]);
    }
    free(mat);

    return 0;
}

Liberando

Toda memória alocada dinamicamente com malloc, calloc ou realloc deve ser liberada com free quando não for mais necessária. Caso contrário, ocorre vazamento de memória (memory leak). A sintaxe é simples:

free(ponteiro);

Regras importantes:

  • Nunca libere o mesmo ponteiro duas vezes (undefined behavior).
  • Após liberar, o ponteiro se torna inválido; atribua NULL a ele para evitar uso acidental.
  • Para matrizes dinâmicas, libere primeiro as linhas (cada mat[i]) e depois o array de ponteiros (mat).
  • Sempre libere na ordem inversa da alocação.

Exemplo de função segura para liberar matriz:

void free_matrix(int **mat, int linhas) {
    if (mat == NULL) return;
    for (int i = 0; i < linhas; i++) {
        free(mat[i]);
    }
    free(mat);
}

Boas práticas e observações finais

  • Sempre verifique o retorno de malloc, calloc e realloc.
  • Evite vazamentos: para cada malloc deve haver um free.
  • Use calloc se precisar de inicialização com zero.
  • Documente seu código: indique quem é responsável por liberar a memória.
  • Considere usar estruturas que encapsulam o array dinâmico (como um vetor em C++) para gerenciar automaticamente.

Referências

Exercícios

  1. Escreva um programa que leia um número inteiro n do usuário, aloque dinamicamente um array de n floats, preencha com valores lidos do teclado e calcule a média. Não esqueça de liberar a memória.

    ✓ Resposta:
    #include <stdio.h>
    #include <stdlib.h>
    
    int main() {
        int n;
        printf("Quantos números? ");
        scanf("%d", &n);
    
        float *arr = (float*) malloc(n * sizeof(float));
        if (arr == NULL) { printf("Erro de alocação\n"); return 1; }
    
        float soma = 0;
        for (int i = 0; i < n; i++) {
            scanf("%f", &arr[i]);
            soma += arr[i];
        }
    
        printf("Média: %.2f\n", soma / n);
        free(arr);
        return 0;
    }
  2. Implemente uma função int* inserir_ordenado(int *arr, int *tam, int valor) que insere valor em um array ordenado (crescente) e redimensiona o array com realloc. O parâmetro tam é um ponteiro para o tamanho atual, que deve ser atualizado. Retorne o novo ponteiro (ou NULL em erro).

    ✓ Resposta:
    #include <stdlib.h>
    
    int* inserir_ordenado(int *arr, int *tam, int valor) {
        int novo_tam = *tam + 1;
        int *temp = (int*) realloc(arr, novo_tam * sizeof(int));
        if (temp == NULL) return NULL;
    
        // desloca elementos para abrir espaço
        int i = *tam - 1;
        while (i >= 0 && temp[i] > valor) {
            temp[i + 1] = temp[i];
            i--;
        }
        temp[i + 1] = valor;
    
        *tam = novo_tam;
        return temp;
    }
  3. Crie uma matriz dinâmica de caracteres (strings) para armazenar uma lista de nomes. O programa deve ler um número n de nomes e depois cada nome (máximo 99 caracteres). Armazene cada nome em uma linha alocada dinamicamente. Ao final, imprima os nomes e libere toda a memória.

    ✓ Resposta:
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main() {
        int n;
        printf("Quantos nomes? ");
        scanf("%d", &n);
        getchar(); // limpa o newline
    
        char **nomes = (char**) malloc(n * sizeof(char*));
        if (nomes == NULL) return 1;
    
        char buffer[100];
        for (int i = 0; i < n; i++) {
            printf("Nome %d: ", i+1);
            fgets(buffer, 100, stdin);
            buffer[strcspn(buffer, "\n")] = 0; // remove newline
            nomes[i] = (char*) malloc((strlen(buffer)+1) * sizeof(char));
            if (nomes[i] == NULL) { /* tratar erro */ }
            strcpy(nomes[i], buffer);
        }
    
        printf("\nNomes lidos:\n");
        for (int i = 0; i < n; i++) {
            printf("%s\n", nomes[i]);
        }
    
        for (int i = 0; i < n; i++) {
            free(nomes[i]);
        }
        free(nomes);
        return 0;
    }
  4. Escreva um programa que leia uma sequência de números inteiros até que o usuário digite 0, armazenando-os em um array dinâmico que dobra de capacidade quando necessário. Ao final, exiba os números na ordem inversa. Use realloc para redimensionar.

    ✓ Resposta:
    #include <stdio.h>
    #include <stdlib.h>
    
    int main() {
        int cap = 2;
        int *arr = (int*) malloc(cap * sizeof(int));
        if (arr == NULL) return 1;
        int count = 0;
        int valor;
    
        printf("Digite números (0 para parar):\n");
        while (1) {
            scanf("%d", &valor);
            if (valor == 0) break;
            if (count == cap) {
                cap *= 2;
                int *temp = (int*) realloc(arr, cap * sizeof(int));
                if (temp == NULL) { free(arr); return 1; }
                arr = temp;
            }
            arr[count++] = valor;
        }
    
        printf("Ordem inversa: ");
        for (int i = count - 1; i >= 0; i--) {
            printf("%d ", arr[i]);
        }
        printf("\n");
    
        free(arr);
        return 0;
    }
  5. Implemente uma função que receba uma matriz dinâmica de inteiros (int **mat), o número de linhas e colunas, e retorne uma nova matriz (alocada dinamicamente) que seja a transposta da original. A função deve ter o protótipo: int** transpor(int **mat, int linhas, int colunas). Teste com uma matriz 2x3.

    ✓ Resposta:
    #include <stdlib.h>
    
    int** transpor(int **mat, int linhas, int colunas) {
        int **t = (int**) malloc(colunas * sizeof(int*));
        if (t == NULL) return NULL;
        for (int i = 0; i < colunas; i++) {
            t[i] = (int*) malloc(linhas * sizeof(int));
            if (t[i] == NULL) {
                for (int j = 0; j < i; j++) free(t[j]);
                free(t);
                return NULL;
            }
        }
        for (int i = 0; i < linhas; i++) {
            for (int j = 0; j < colunas; j++) {
                t[j][i] = mat[i][j];
            }
        }
        return t;
    }
    
    // Exemplo de uso:
    #include <stdio.h>
    int main() {
        int l=2, c=3;
        int **m = (int**) malloc(l * sizeof(int*));
        for (int i=0; i<l; i++) m[i] = (int*) malloc(c * sizeof(int));
        // preenche
        for (int i=0; i<l; i++)
            for (int j=0; j<c; j++)
                m[i][j] = i*c + j;
    
        int **t = transpor(m, l, c);
        if (t) {
            for (int i=0; i<c; i++) {
                for (int j=0; j<l; j++)
                    printf("%d ", t[i][j]);
                printf("\n");
            }
            // libera t
            for (int i=0; i<c; i++) free(t[i]);
            free(t);
        }
        // libera m
        for (int i=0; i<l; i++) free(m[i]);
        free(m);
        return 0;
    }