Ponteiros duplos e matrizes dinâmicas
1. Fundamentos de Ponteiros Duplos
1.1. Declaração e sintaxe
Um ponteiro duplo é declarado com dois asteriscos: int **ptr;. Isso significa que ptr é um ponteiro que armazena o endereço de outro ponteiro do tipo int*. Em outras palavras, é um ponteiro para um ponteiro para um inteiro.
int valor = 42;
int *ptr_simples = &valor;
int **ptr_duplo = &ptr_simples;
1.2. Representação em memória
Na memória, um ponteiro duplo segue a seguinte hierarquia:
- ptr_duplo armazena o endereço de ptr_simples
- ptr_simples armazena o endereço de valor
- valor contém o dado efetivo (42)
Para acessar o valor final, é necessário fazer uma desreferenciação dupla:
printf("%d\n", **ptr_duplo); // Imprime 42
printf("%d\n", *ptr_simples); // Também imprime 42
1.3. Relação com arrays unidimensionais
Em C, um array de ponteiros int *arr[] é equivalente a um ponteiro duplo int **arr quando passado como argumento para uma função. Isso ocorre porque o nome de um array decai para um ponteiro para seu primeiro elemento.
int *vetor[5]; // Array de 5 ponteiros para int
int **p = vetor; // Válido: vetor decai para int**
2. Alocação Dinâmica de Matrizes com Ponteiro Duplo
2.1. Alocação passo a passo
Para alocar uma matriz dinâmica de linhas x colunas:
int **matriz;
int linhas = 3, colunas = 3;
// Passo 1: alocar um array de ponteiros (as linhas)
matriz = (int**) malloc(linhas * sizeof(int*));
// Passo 2: para cada linha, alocar um array de inteiros (as colunas)
for (int i = 0; i < linhas; i++) {
matriz[i] = (int*) malloc(colunas * sizeof(int));
}
2.2. Liberação de memória
A liberação deve seguir a ordem inversa da alocação:
// Liberar cada linha primeiro
for (int i = 0; i < linhas; i++) {
free(matriz[i]);
}
// Depois liberar o ponteiro principal
free(matriz);
2.3. Exemplo prático: matriz 3x3
#include <stdio.h>
#include <stdlib.h>
int main() {
int **mat;
int lin = 3, col = 3;
int count = 1;
// Alocação
mat = (int**) malloc(lin * sizeof(int*));
for (int i = 0; i < lin; i++) {
mat[i] = (int*) malloc(col * sizeof(int));
}
// Preenchimento
for (int i = 0; i < lin; i++) {
for (int j = 0; j < col; j++) {
mat[i][j] = count++;
}
}
// Exibição
for (int i = 0; i < lin; i++) {
for (int j = 0; j < col; j++) {
printf("%d ", mat[i][j]);
}
printf("\n");
}
// Liberação
for (int i = 0; i < lin; i++) {
free(mat[i]);
}
free(mat);
return 0;
}
3. Acesso e Manipulação de Elementos
3.1. Indexação
Existem duas formas equivalentes de acessar elementos:
// Forma 1: notação de array (mais legível)
mat[i][j] = 10;
// Forma 2: aritmética de ponteiros (equivalente)
*(*(mat + i) + j) = 10;
3.2. Percorrimento com laços aninhados
for (int i = 0; i < linhas; i++) {
for (int j = 0; j < colunas; j++) {
printf("Elemento [%d][%d] = %d\n", i, j, mat[i][j]);
}
}
3.3. Passagem de matriz dinâmica para funções
void imprime_matriz(int **mat, int lin, int col) {
for (int i = 0; i < lin; i++) {
for (int j = 0; j < col; j++) {
printf("%3d ", mat[i][j]);
}
printf("\n");
}
}
// Chamada
imprime_matriz(matriz, linhas, colunas);
4. Matrizes Irregulares (Jagged Arrays)
4.1. Conceito
Matrizes irregulares permitem que cada linha tenha um número diferente de colunas, otimizando o uso de memória.
4.2. Alocação
int **jagged;
int linhas = 3;
int tamanhos[] = {2, 4, 3}; // Cada linha com tamanho diferente
jagged = (int**) malloc(linhas * sizeof(int*));
for (int i = 0; i < linhas; i++) {
jagged[i] = (int*) malloc(tamanhos[i] * sizeof(int));
}
4.3. Caso de uso
Útil para armazenar strings de comprimentos variáveis ou dados esparsos:
char **nomes;
nomes = (char**) malloc(3 * sizeof(char*));
nomes[0] = (char*) malloc(5 * sizeof(char)); // "João"
nomes[1] = (char*) malloc(3 * sizeof(char)); // "Ana"
nomes[2] = (char*) malloc(7 * sizeof(char)); // "Mariana"
5. Ponteiros Duplos em Funções
5.1. Modificação de ponteiros dentro de funções
Para modificar um ponteiro dentro de uma função, passamos seu endereço:
void aloca_matriz(int ***mat, int lin, int col) {
*mat = (int**) malloc(lin * sizeof(int*));
for (int i = 0; i < lin; i++) {
(*mat)[i] = (int*) malloc(col * sizeof(int));
}
}
// Uso
int **minha_matriz;
aloca_matriz(&minha_matriz, 3, 3);
5.2. Retorno de matrizes alocadas
int** cria_matriz(int lin, int col) {
int **mat = (int**) malloc(lin * sizeof(int*));
for (int i = 0; i < lin; i++) {
mat[i] = (int*) malloc(col * sizeof(int));
}
return mat;
}
5.3. Exemplo completo
int** preenche_matriz(int lin, int col) {
int **mat = cria_matriz(lin, col);
for (int i = 0; i < lin; i++) {
for (int j = 0; j < col; j++) {
mat[i][j] = i * col + j;
}
}
return mat;
}
6. Erros Comuns e Boas Práticas
6.1. Vazamentos de memória
Sempre libere cada linha individualmente antes de liberar o ponteiro principal:
// ERRADO: libera apenas o ponteiro principal
free(mat); // As linhas continuam alocadas!
// CORRETO
for (int i = 0; i < lin; i++) free(mat[i]);
free(mat);
6.2. Acesso inválido
Sempre verifique índices e ponteiros nulos:
if (mat == NULL) {
printf("Erro: matriz não alocada\n");
return;
}
if (i >= linhas || j >= colunas) {
printf("Erro: índice fora dos limites\n");
return;
}
6.3. Uso correto de free
// Verificar NULL antes de liberar
if (mat != NULL) {
for (int i = 0; i < lin; i++) {
if (mat[i] != NULL) {
free(mat[i]);
mat[i] = NULL; // Boa prática
}
}
free(mat);
mat = NULL;
}
7. Comparação com Matrizes Estáticas
7.1. Diferenças de alocação
| Característica | Matriz Estática | Matriz Dinâmica |
|---|---|---|
| Local na memória | Pilha (stack) | Heap |
| Tamanho | Fixo em compilação | Variável em execução |
| Liberação | Automática | Manual (free) |
| Velocidade | Mais rápida | Mais lenta (indireção) |
7.2. Vantagens da matriz dinâmica
- Tamanho definido em tempo de execução
- Uso eficiente de memória (aloca apenas o necessário)
- Possibilidade de redimensionamento
- Matrizes irregulares
7.3. Desvantagens
- Overhead de gerenciamento manual de memória
- Risco de vazamentos de memória
- Fragmentação da memória heap
- Acesso mais lento devido à dupla indireção
// Matriz estática (tamanho fixo)
int estatica[3][4]; // Sempre 3x4
// Matriz dinâmica (tamanho variável)
int **dinamica;
int l = obter_tamanho(); // Em tempo de execução
dinamica = (int**) malloc(l * sizeof(int*));
Referências
- Documentação oficial do GCC sobre alocação dinâmica — Guia completo sobre funções malloc, calloc, realloc e free no compilador GCC
- C Programming - Pointer to Pointer (Double Pointer) — Tutorial detalhado da GeeksforGeeks sobre ponteiros duplos com exemplos práticos
- Dynamic 2D Array Allocation in C — Explicação passo a passo da alocação dinâmica de matrizes 2D no Programiz
- C Dynamic Memory Allocation — Tutorial abrangente do TutorialsPoint sobre alocação dinâmica de memória em C
- Double Pointers and Dynamic Memory in C — Notas de aula da Universidade de Yale sobre ponteiros duplos e gerenciamento de memória
- Understanding and Using C Pointers — Livro técnico da O'Reilly sobre ponteiros em C, incluindo capítulos sobre ponteiros duplos