Entendo ponteiros e ponteiros de ponteiros na linguagem C.

Thiago Cardoso
7 min readApr 14, 2021

Conhecendo e iterando pelas strings e coleções de strings

Photo by paolo candelo on Unsplash

Para entender a lógica dos ponteiros na linguagem C, assim como conseguir visualizar o funcionamento de uma string, busquei responder às perguntas abaixo:

  • Qual a diferença entre uma variável “normal” e um ponteiro?
  • Como itirar ou “caminhar” dentro de um ponteiro
  • O que é uma string?
  • O que é um ponteiro de ponteiros e como iterar dentro e entre eles?

Abaixo estão algumas respostas para essas perguntas. Busquei priorizar uma resposta mais didática que formal. Nesse sentido, talvez existam erros conceituais.

1 — Qual a diferença entre uma variável “normal” e um ponteiro?

Uma variável aloca um VALOR em um endereço de mémoria.

Quando fazemos:

char c;

c = ‘a’;

i ) o compilador vai caçar um endereço vazio na memória (mais exatamente no stack, ou na parte alta da memória), e

ii) o compilador vai alocar o char ‘a’ no endereço de memória da variável c criada.

Pense em um daqueles armários de academia e que o compilador é uma pessoa que trabalha guardando pertences nesses armários. Quando você chega nessa academia, essa pessoa registra seu nome e procura um armário vazio para você. Esse armário terá um endereço, ou um número de identificação. Em seguida, você diz e entrega o objeto que irá colocar dentro do armário. O objeto é o mesmo que o valor que fica alocado dentro de um endereço em sua memória.

Um ponteiro, por sua vez, ALOCA UM ENDEREÇO em um endereço de memória. É como se o valor de um ponteiro fosse o endereço de alguma outra variável.

Voltando no exemplo da academia, suponha que, ao invés de guardar um objeto dentro do seu armário, você decide guardar o número de identificação e a chave de um outro armário. Quando você pede para a pessoa pegar o que está dentro do seu armário, ela vai voltar com uma chave e um número de identificação de um segundo armário. Você pode pedir então para pessoa pegar o objeto que está nesse segundo armário, tendo em vista que ela tem o número de identificação e a chave. Como o compilador não é uma pessoa, ele não vai ficar puto da cara contigo.

Quando fazemos, por exemplo:

char *pont;

pont = &c;

i) o compilador vai caçar um endereço vazio na memória (mais exatamente no stack, ou na parte alta da memória de seu RAM); e

ii) o compilador vai alocar o ENDEREÇO do char c no endeço de memória da ponteiro *pont. O ‘&’ significa que queremos o ENDEREÇO do c, não seu valor.

iii) Se você quiser saber o valor do ponteiro, basta incluir o ‘*’ antes do nome do seu ponteiro. No caso, *pont irá retornar o valor alocado no endereço de memória da variável c (o que no caso é o char ‘a’). pont, sem asterisco, irá retornar o endereço da variável c.

iv) Se você tentar alocar um valor em um ponteiro vai dar problema. Por exemplo: pont = c . PEEEM. Não funciona. Um ponteiro aceita apenas ENDEREÇOS de memória como seu “valor”.

Na analogia da academia, talvez um melhor exemplo é que um ponteiro , ao invés de um armário, é como se fosse aquele painel onde ficam todas as chaves dos armários. Não dá para colocar objetos nesse painel de chaves, apenas chaves com seus números de identificação. Contudo, sabendo onde está uma determinada chave, você irá ter o número de identificação de um armário para qual essa chave “aponta” (e a possibilidade de alocar ou retirar um objeto desse armário).

2 — E como iterar ou “caminhar” dentro de um ponteiro?

Essa é uma questão que eu quebrei a cabeça por um tempo. O ponteiro tem como seu valor um ENDEREÇO de memória. Em geral de alguma outra variável. Por exemplo:

int i;

int *pont;

i = 1;

pont = &i;

E bem óbvio que se você somar 1 ao valor de uma variável, em especial se essa variavel for um int, você vai ter um novo número, incrementado por 1. Por exemplo:

i + 1 == 2;

No caso de um ponteiro, seu valor é um ENDEREÇO. Adicionar 1 siginifica adicionar 1 ao seu endereço. Nesse caso, pulamos para o PRÓXIMO ENDEREÇO NA MEMÓRIA. Por sorte, nosso amigo compilador já reconhece que se tivermos um ponteiro de char ele vai pular apenas 1 byte (algo como 8 bites ou “casinhas”, tendo em vista que 1 byte = bites) . Se tivermos um ponteiro de int, vai pular 4 bytes (em geral), e assim vai. Portanto, suponha que tenhamos:

int a;

int b;

pont = &a;

O valor de pont + 1 vai ser justamento o endereço de b! Porquê b? Pois b foi a variável alocada imediante após o a. Portanto, estará no endereço de memória SEGUINTE ao a.

Isso é MUITO útil quando queremos itinerar por uma STRING ou CADEIA DE CARACTERES. No C NÃO EXISTE UMA VARIÁVEL DO TIPO STRING. Existem apenas os chars. Uma string é apenas um sequência de chars, finalizado com um char que representa o vazio ‘\0’.

Agora imagine que se para criar uma palavra ou texto fosse necessário declarar CADA UM DOS CARACTERES e inicializar seu valor. Meu Deus. No C conseguimos facilitar muito isso com o uso do ponteiro. Funciona assim:

char *pont;

pont = “buenas madrugadas”

Note que não existe um char igual a “buenas madrugadas” (afinal cabe apenas uma letra em um char!). Na verdade esse é um conjunto de vários chars, um seguido do outro. O C está nos ajudando aqui. A mágica que está sendo feita por baixo dos panos é que cada um desses caracteres está sendo inicializado e tendo um valor alocado (o ‘b’, o ‘u’, o ‘e’ e assim por diante) em vários endereços subsequentes (um atrás do outro).

Para não termos que escrever no papel de pão cada endereço de cada um desses valores ou precisar dar um nome para cada um desses chars, o ponteiro é uma mão na roda.

3 — Chegamos já na resposta da terceira pergunta: O que é e como funciona uma string?

Uma string é um conjunto de caracteres, alocados em endereços de memória “vizinhos”.

O ponteiro vai nos dizer qual o ENDEREÇO DO PRIMEIRO CHAR dessa nossa string, ou conjunto de caracteres.

Se sabemos o endereço do primeiro char, que é o endereço para o qual o ponteiro aponta, para saber o segundo endereço do segundo char basta fazer então: pont + 1. Para saber o endereço do terceiro char, basta fazer pont + 2:

Se você quiser saber o valor (e não o endereço) de um char n qualquer, basta acrescentar o ‘*’. No nosso exemplo:

*(pont + 2) == ‘e’.

4 — Por fim, o que é um ponteiro de ponteiros e como iterar dentro e entre strings?

Primeiro, o que é um ponteiro de ponteiros?

Lembrando que no caso:

char *pont;

pont = “buenas”

pont é um ponteiro que nos diz o endereço do primeiro char da string “buenas”, no caso o char ‘b’.

Agora vamos imaginar que temos um conjunto de STRINGS e não de CHARs. Esse conjunto de strings possui 2 strings: “buenas” e “madrugradas”. Como representar isso? É ai que entra o ponteiro de ponteiros.

Veja que agora não temos uma única cadeia de chars “buenas madrugadas”. Temos primeiro uma cadeia de chars “buenas” e depois outra cadeia de chars “madrugadas”. O ponteiro de ponteiros no ajuda a circular ENTRE essas strings e DENTRO dessas strings. Para inicializar um ponteiro de ponteiros basta adicionar um ‘*’ em relação ao ponteiro:

char **pont_de_pont;

Como caminhar ENTRE as strings?

Vamos supor que você quer printar os chars do segunda string “madrugadas”. Como chegar la?

Se um ponteiro aponta para um ENDEREÇO, um ponteiro de ponteiros aponta para o ENDEREÇO DE UM ENDEREÇO.

Mas como assim um ENDEREÇO DE ENDEREÇO?

Lembre que o endereço é o lugar na mémoria física de seu computador onde está guardado algo. Um ponteiro tem um endereço na sua memória também. Nesse endereço vai estar “escrito” o endereço de outra variável, para a qual seu ponteiro está apontando.

No caso de um ponteiro de ponteiros, vamos ter o endereço de um outro endereço. Se esse segundo endereço for uma string, podemos ter então o endereço dessa string. E o que é o endereço de uma string: o endereço de seu primeira char.

‘pont_de_pont’ nos dá o endereço da primeira string de nossa coleção de strings. A primeira string é “buenas”. O endereço dessa string é o endereço de seu primeiro char, ‘b’. Logo, **pont_de_pont == ‘b’. Lembrando que colocar o ‘*’ nos traz o valor que está dentro do endereço.

Logo:

pont_de_pont + 1 nos dá o endereço da segunda string de nossa coleção de string

*(pont_de_pont + 1) nos dá o endereço do primeiro char da segunda strings

E, finalmente:

*(pont_de_pont + 1) + 1 nos dá o endereço do segunda char da segunda string

*(*(pont_de_pont + 1) + 1) nos dá o valor do segundo char da segunda string

Para generalizar fica fácil: troque o primeiro 1 pelo número da string e o segundo 1 pelo número do caracetere que você quer encontrar :D

  • (*(pont _de_pont + x) + y) nos dá o valor do char número y da string número x

--

--