Aviso: Você precisa saber o básico de C para entender este texto, várias pessoas tem dificuldade em ponteiros e espero que isso seja útil, recomendo os livros C Completo e Total, Curso de programação em C da UFMG, O Fantástico mundo da linguagem C, se quiser se aprofundar, siga com o curso da USP

ponteiro Um assunto muitas vezes ignorado na graduação ou mesmo em livros que passamos batido, é a indireção múltipla. Um recurso da linguagem C/C++ que permite criarmos até mesmo estruturas de dados complexas que sejam dinâmicas ou códigos mais enxutos e elásticos, onde podemos fazer vários formatos na estrutura de dado, algo bem abstrato.

Um ponteiro é um tipo de variável que não aponta para o valor de uma variável em si, mas sim para um endereço de memória. O mais interessante, é que o ponteiro é uma variável que tem um endereço, então um ponteiro de ponteiro armazena endereços de ponteiros e o limite disso depende do compilador que você está utilizando.

Porém, a ideia central aqui é criar um ponteiro de ponteiro para um ponteiro, ou seja, criar mais níveis de apontamento, contribuindo para que seu código fique ainda mais dinâmico.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdlib.h>
#include <stdio.h>

void  main (){

    char *ponteiro = NULL;
    char **ponteiro_de_ponteiro = NULL;
    char ***ponteiro_de_ponteiro_para_ponteiro = NULL;
    char letra = 'L';

    ponteiro = &letra; //o parâmetro & indica que o que será passado é
    //o endereço da variável letra e não seu conteúdo
    ponteiro_de_ponteiro = &ponteiro;
    ponteiro_de_ponteiro_para_ponteiro = &ponteiro_de_ponteiro;

    printf ("A letra é: %c\n", ***ponteiro_de_ponteiro_para_ponteiro);
}

Quando executamos esse código, a saída resultante é “A letra é: L”, porém não utilizamos a referência da variável letra, mas sim o endereço físico onde a variável está armazenada, algo que não é muita novidade, porque se vocẽ está aqui, provavelmente já conhece ponteiros. Este é um exemplo básico de indireção múltipla, onde usamos ponteiros para referenciar outros ponteiros. Mas olhando desta forma, parece ser algo até mesmo trivial, não é mesmo?

Apontando pra ponteiros em massa - matrizes

Começa a ficar interessante, quando começamos a pensar na possibilidade de apontar para outros ponteiros em massa, sendo assim, poderíamos consumir quanta memória desejássemos e ainda por cima, teríamos uma estrutura onde poderíamos organizar nossas informações! Aqui um outro exemplo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <stdio.h>
#include <stdlib.h>

void main (int argc, char *argv[]){

    char letra = 'L';
    char **ponteiro_de_ponteiro = NULL; //precisamos apenas de um
    //ponteiro para ponteiro nesse caso
    int tamanho, i, j;

    tamanho = atoi(argv[1]);
    
    //Assim podemos armazenar ponteiros de ponteiros
    ponteiro_de_ponteiro = (char**)malloc (sizeof(char*)*tamanho);

    for (i = 0; i < tamanho; i++){
    	//assim criamos blocos de caractere dentro dos ponteiros
        ponteiro_de_ponteiro[i] = (char*) malloc (sizeof(char)*tamanho);
    }

	//com o auxílio desse for, conseguimos preencher toda a matriz
    for (i = 0; i < tamanho; i++){
        for (j = 0; j < tamanho; j++){
         ponteiro_de_ponteiro[i][j] = letra;
        }        
    }
    
    //Aqui é um teste de prova de que está preenchido
    for (i = 0; i < tamanho; i++){
        for (j = 0; j < tamanho; j++){
            printf ("%c", ponteiro_de_ponteiro[i][j]);
        }
    }
}

Ponteiros de ponteiros para ponteiros - N dimensões

Um professor numa aula da graduação, passou o seguinte problema para a sala: A gente deveria criar um programa que armazenasse o o nome do aluno, o número de matrícula, a turma, três provas e o ano da turma, além disso, calcular uma média simples de cada aluno, a média da turma e o comparativo entre os anos da turma, tudo isso, armazenando 5 anos anos de turma com no mínimo 3 turmas para cada ano.

Diante deste problema, comecei a pensar um jeito mais simples de resolvê-lo, não queria fazer uma coisa simples e trabalhosa, queria fazer uma coisa que fosse mais complexa de se pensar, mas ao mesmo tempo mais fácil de implementar. Então, recorri à indireção múltipla! Você pode ver o código completo aqui.

Porém, podemos ver também o seguinte exemplo de forma mais simples:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <stdio.h>
#include <stdlib.h>

int main (int argc, char *argv[]){

    char letra = 'L';
    char ***ponteiro_de_ponteiro_para_ponteiro = NULL;
    int tamanho, i, j, k;

    tamanho = atoi(argv[1]);
    ponteiro_de_ponteiro_para_ponteiro = \
        (char***)malloc (sizeof(char**)*tamanho);

    for (i = 0; i < tamanho; i++){
        ponteiro_de_ponteiro_para_ponteiro[i] = \
            (char**) malloc (sizeof(char*)*tamanho);
        for (j = 0; j < tamanho; j++){
            ponteiro_de_ponteiro_para_ponteiro[i][j] = \
                (char*) malloc (sizeof(char)*tamanho);
        }
    }

    for (i = 0; i < tamanho; i++){
        for (j = 0; j < tamanho; j++){
            for(k = 0; k < tamanho; k++)
         ponteiro_de_ponteiro_para_ponteiro[i][j][k] = letra;
        }        
    }
    
    for (i = 0; i < tamanho; i++){
        for (j = 0; j < tamanho; j++){
            for (k = 0; k < tamanho; k++)
            printf ("%c", ponteiro_de_ponteiro_para_ponteiro[i][j][k]);
        }
    }
    printf ("\n");

    return 1;
}

Mas afinal, o que está acontecendo? Na linha 7, quando declaramos um ponteiro de ponteiro para ponteiro, usamos ***, o que significa que é uma informação tridimencional, a quantidade de * significa exatamente isso! Depois, na linha 15 e 18, preenchemos o espaço de ponteiro para ponteiro e ponteiros, observe que o primeiro armazenamento faz uma conversão usando (char***), depois (char**) e por fim (char*), isso mostra qual tipo de informação será armazenada em cada bloco. Então, podemos nos perguntar:

Quantos níveis a informação que quero armazenar tem?

Depois disso, podemos criar blocos de memória com o design que quisermos e o melhor de tudo: eles serão dinâmicos, não precisamos ficar ajustando o código para ler algo com tamanho diferente. Uma das situações que isso pode se colocar de forma muito útil, é o armazenamento em massa de ponteiros para arquivos de um sistema operacional, para estruturas de dados (lista simplesmente encadeada, árvore binária, árvore B ou afins), ou mesmo, pasmem: armazenar o endereço de funções do seu programa em C :)

Conclusão

A utilização de indireção múltipla, muitas vezes tratada como nota de rodapé ou simplesmente ignorada, é uma função da linguagem C/C++ que possibilita organizar e fazer coisas muito belas, como por exemplo organizar dados de forma multidimensional.