Ponteiros para ponteiros
Esta aula aborda o conceito de ponteiros para ponteiros em C, explicando a dupla indireção, seus usos comuns, exemplos práticos e cuidados necessários. O aluno aprenderá a declarar, atribuir e utilizar ponteiros de ponteiros para manipular dados indiretamente.
Em C, um ponteiro para ponteiro é uma variável que armazena o endereço de outro ponteiro. Isso cria um nível adicional de indireção, permitindo que funções modifiquem ponteiros passados como argumentos, ou que estruturas de dados como matrizes de strings sejam manipuladas eficientemente.
Entender ponteiros para ponteiros é essencial para dominar alocação dinâmica de memória, listas encadeadas e argumentos de funções que precisam alterar o ponteiro original.
Dupla indireção (**)
A dupla indireção é representada por dois asteriscos (**) na declaração. Por exemplo, int **ptr; declara um ponteiro para um ponteiro de inteiro. Isso significa que ptr aponta para uma variável que, por sua vez, aponta para um inteiro.
Graficamente, temos: ptr → *ptr (um ponteiro) → **ptr (o valor inteiro). Para acessar o valor final, usamos dois operadores de dereferência: **ptr.
#include <stdio.h>
int main() {
int a = 42;
int *p = &a; // ponteiro para int
int **pp = &p; // ponteiro para ponteiro
printf("a = %d\n", a); // 42
printf("*p = %d\n", *p); // 42
printf("**pp = %d\n", **pp); // 42
return 0;
}Casos de uso
Um uso comum é em funções que precisam modificar o ponteiro passado como argumento. Por exemplo, ao alocar memória dentro de uma função, o ponteiro original deve ser alterado; para isso, passamos o endereço do ponteiro (um ponteiro para ponteiro).
Outro caso é a manipulação de matrizes de strings, onde um array de ponteiros para char pode ser passado como char **. Além disso, estruturas de dados como listas encadeadas usam ponteiros duplos para inserir ou remover nós no início da lista sem perder a referência.
// Função que aloca um vetor de inteiros
#include <stdlib.h>
void aloca_vetor(int **vetor, int n) {
*vetor = (int *)malloc(n * sizeof(int));
if (*vetor == NULL) {
exit(1);
}
// preenche com zeros
for (int i = 0; i < n; i++) {
(*vetor)[i] = 0;
}
}
int main() {
int *v = NULL;
aloca_vetor(&v, 10);
// agora v aponta para um vetor de 10 inteiros
free(v);
return 0;
}Exemplos
Exemplo 1: Modificar um ponteiro em uma função. A função troca_ponteiro recebe um ponteiro duplo e altera para onde o ponteiro original aponta.
#include <stdio.h>
void troca_ponteiro(int **pp, int *novo) {
*pp = novo; // altera o ponteiro original
}
int main() {
int a = 10, b = 20;
int *p = &a;
printf("Antes: *p = %d\n", *p); // 10
troca_ponteiro(&p, &b);
printf("Depois: *p = %d\n", *p); // 20
return 0;
}Exemplo 2: Matriz de strings. Um array de strings é um char *[], que decai para char ** ao ser passado para uma função.
#include <stdio.h>
void imprime_strings(char **strings, int n) {
for (int i = 0; i < n; i++) {
printf("%s\n", strings[i]);
}
}
int main() {
char *nomes[] = {"Ana", "Beto", "Carla"};
imprime_strings(nomes, 3);
return 0;
}Cuidados
Ao usar ponteiros para ponteiros, é fácil cometer erros de dereferência. Sempre verifique se o ponteiro intermediário não é nulo antes de dereferenciá-lo. Por exemplo, **pp só é seguro se pp não for NULL e *pp não for NULL.
Outro cuidado: ao alocar memória para um ponteiro duplo, lembre-se de alocar primeiro o ponteiro externo e depois o interno. Por exemplo, para uma matriz 2D alocada dinamicamente:
int **matriz = (int **)malloc(linhas * sizeof(int *));
for (int i = 0; i < linhas; i++) {
matriz[i] = (int *)malloc(colunas * sizeof(int));
}Não se esqueça de liberar a memória na ordem inversa: primeiro as linhas, depois a matriz.
Boas práticas
Use nomes de variáveis que indiquem o nível de indireção, como pp para ponteiro duplo. Sempre inicialize ponteiros com NULL e verifique antes de usar. Evite níveis excessivos de indireção (mais de dois) para não prejudicar a legibilidade.
Referências
- cppreference: Pointer declaration
- GeeksforGeeks: Double Pointer (Pointer to Pointer) in C
- TutorialsPoint: C - Pointer to Pointer
- Programiz: C Pointer to Pointer (Double Indirection)
- IBM Documentation: Double pointers
Exercícios
Crie uma função que receba um ponteiro duplo para um inteiro e altere o valor do inteiro original para 100. Teste no main.
✓ Resposta:#include <stdio.h> void altera_valor(int **pp) { **pp = 100; } int main() { int x = 10; int *p = &x; altera_valor(&p); printf("x = %d\n", x); // 100 return 0; }Escreva uma função que receba um ponteiro duplo para char (string) e troque a string para "Trocado".
✓ Resposta:#include <stdio.h> #include <string.h> void troca_string(char **pp) { *pp = "Trocado"; } int main() { char *s = "Original"; troca_string(&s); printf("%s\n", s); // Trocado return 0; }Implemente uma função que aloca um vetor de n inteiros e retorna o ponteiro por meio de um ponteiro duplo. Use malloc.
✓ Resposta:#include <stdlib.h> void aloca_vetor(int **vetor, int n) { *vetor = (int *)malloc(n * sizeof(int)); if (*vetor == NULL) exit(1); } int main() { int *v = NULL; aloca_vetor(&v, 5); free(v); return 0; }Crie uma função que receba uma matriz 2D representada como ponteiro duplo (int **) e imprima seus elementos. Considere linhas e colunas fixas.
✓ Resposta:#include <stdio.h> void imprime_matriz(int **mat, int linhas, int colunas) { for (int i = 0; i < linhas; i++) { for (int j = 0; j < colunas; j++) { printf("%d ", mat[i][j]); } printf("\n"); } } int main() { int a[2][3] = {{1,2,3},{4,5,6}}; int *ptr[2]; ptr[0] = a[0]; ptr[1] = a[1]; imprime_matriz(ptr, 2, 3); return 0; }Explique por que o código abaixo causa um erro e corrija-o:
int **pp; *pp = malloc(sizeof(int)); **pp = 42;✓ Resposta:O ponteiro duploppnão foi inicializado; ele contém lixo, então dereferenciá-lo (*pp) é inválido. Correção:int *p = malloc(sizeof(int)); int **pp = &p; **pp = 42; free(p);