C Ponteiro para Ponteiro
Um ponteiro é uma variável que guarda o endereço de outra variável. Mas o ponteiro também é uma variável: ele mora em algum lugar da memória e tem o seu próprio endereço. Então nada impede que outro ponteiro guarde esse endereço. Isso é um ponteiro para ponteiro, escrito com dois asteriscos: int **pp. Parece um trava-língua, mas resolve um problema bem concreto: deixar uma função mudar um ponteiro de quem a chamou. E é exatamente o tipo do famoso char **argv do main.
- O que é
int **ppe como desenhá-lo na memória (três caixas, duas setas). - A diferença entre
pp,*ppe**pp. - Por que uma função que recebe
int *pnão consegue mudar o ponteiro de quem chamou, e comoint **presolve. - Onde o
**aparece no dia a dia:char **argv, listas de strings,strtol. - Os erros reais:
incompatible pointer type,%dcom um ponteiro e a falha de segmentação de**ppnum ponteiro nulo.
Esta aula supõe que você já leu Ponteiros e Passar por Ponteiro. Todos os programas foram compilados com o gcc 15.2 na máquina fictícia da Ana (usuária ana, computador notebook), na pasta ~/c, com o comando padrão do guia: gcc -std=c23 -Wall -Wextra arquivo.c -o programa.
A ideia: o bilhete que diz onde está o bilhete
Pense numa caça ao tesouro na festa da Ana. O tesouro (um saquinho com 10 moedas) está escondido na gaveta 44. Quem guarda o segredo é um bilhete que diz "o tesouro está na gaveta 44". Esse bilhete, por sua vez, foi escondido na gaveta 48.
A Ana entrega para a primeira criança um segundo bilhete: "procure o bilhete na gaveta 48". A criança abre a gaveta 48, lê o primeiro bilhete, vai até a gaveta 44 e acha as moedas. Dois saltos.
| Na caça ao tesouro | Em C | Tipo |
|---|---|---|
| O saquinho com 10 moedas | moedas | int |
| O bilhete "o tesouro está na gaveta 44" | p, um ponteiro | int * (ponteiro para int) |
| O bilhete "procure o bilhete na gaveta 48" | pp, um ponteiro para ponteiro | int ** (ponteiro para int *) |
Cada asterisco é um salto. Com p você precisa de um salto até as moedas (*p). Com pp, de dois (**pp).
1. Declarar um ponteiro para ponteiro
O programa abaixo monta a caça ao tesouro e mostra onde cada coisa mora. O %p do printf mostra um endereço (veja Memória e Endereços); o (void *) antes de cada endereço é a forma que o %p pede, explicada em Ponteiro void*.
#include <stdio.h>
int main(void) {
int moedas = 10;
int *p = &moedas;
int **pp = &p;
printf("moedas = %d, mora em %p\n", moedas, (void *)&moedas);
printf("p = %p, mora em %p\n", (void *)p, (void *)&p);
printf("pp = %p, mora em %p\n", (void *)pp, (void *)&pp);
printf("*pp = %p\n", (void *)*pp);
printf("**pp = %d\n", **pp);
return 0;
}
- int moedas = 10;
- Uma variável comum: o tesouro.
- int *p = &moedas;
pé um ponteiro parainte guarda o endereço demoedas. É o primeiro bilhete.- int **pp
- Leia de trás para a frente:
ppé um ponteiro (*) para um ponteiro (*) paraint. O tipo dele éint **. - = &p;
ppguarda o endereço dep(não o demoedas). É o segundo bilhete.- *pp
- Um salto: "o que está guardado no lugar para onde
ppaponta". Lá estáp, então*ppép: o endereço demoedas. - **pp
- Dois saltos:
*(*pp), ou seja,*p, ou seja,moedas: o número 10.
$ gcc -std=c23 -Wall -Wextra pp.c -o pp $ ./pp moedas = 10, mora em 0x7ffc78692c44 p = 0x7ffc78692c44, mora em 0x7ffc78692c48 pp = 0x7ffc78692c48, mora em 0x7ffc78692c50 *pp = 0x7ffc78692c44 **pp = 10
Siga os números: o valor de p (…c44) é o endereço de moedas; o valor de pp (…c48) é o endereço de p. Os endereços mudam a cada execução (o Linux sorteia onde a memória começa), mas essa amarração é sempre a mesma.
*pp; seguir as duas é **pp.A mesma coisa em forma de tabela, do jeito que a memória é de verdade (uma rua de casas numeradas):
| Endereço | Nome | Tipo (tamanho) | O que está guardado |
|---|---|---|---|
0x7ffc78692c44 | moedas | int (4 bytes) | 10 |
0x7ffc78692c48 | p | int * (8 bytes) | 0x7ffc78692c44 → aponta para moedas |
0x7ffc78692c50 | pp | int ** (8 bytes) | 0x7ffc78692c48 → aponta para p |
p começa em …c48 e pp em …c50: a diferença é 8, o tamanho de um ponteiro no Linux de 64 bits. Um ponteiro para ponteiro tem o mesmo tamanho de qualquer outro ponteiro: ele também só guarda um endereço.As três perguntas
| Você escreve | Tipo | Pergunta | Resposta no exemplo |
|---|---|---|---|
pp | int ** | "O que está anotado no segundo bilhete?" | o endereço de p |
*pp | int * | "Siga uma vez: o que está no primeiro bilhete?" | o endereço de moedas (é o próprio p) |
**pp | int | "Siga duas vezes: o que tem no tesouro?" | 10 |
Uma regra prática para nunca se perder: cada * que você escreve na frente do nome tira um * do tipo. pp é int **; *pp é int *; **pp é int. E cada & põe um: moedas é int, &moedas é int *, &p é int **.
int **pp, int** pp e int * * pp são a mesma coisa para o compilador. O guia escreve colado ao nome (int **pp), o estilo mais comum em C, porque numa linha como int **a, *b; o * vale para cada nome separadamente: a é int ** e b é int *.
2. Mudar as coisas pelo ponteiro para ponteiro
Com pp na mão você pode mexer em dois lugares diferentes, dependendo de quantos asteriscos usa:
**pp = 15;muda o valor final (o tesouro).*pp = &outra;muda o ponteiro do meio: agora ele aponta para outra variável.
No jogo da Ana, duas jogadoras disputam uma partida e o ponteiro vez indica de quem é a vez:
#include <stdio.h>
int main(void) {
int ana = 10;
int bia = 50;
int *vez = &ana;
int **pp = &vez;
**pp = 15;
printf("ana = %d, bia = %d, *vez = %d\n", ana, bia, *vez);
*pp = &bia;
printf("ana = %d, bia = %d, *vez = %d\n", ana, bia, *vez);
**pp = 55;
printf("ana = %d, bia = %d, *vez = %d\n", ana, bia, *vez);
return 0;
}
$ gcc -std=c23 -Wall -Wextra mudar.c -o mudar $ ./mudar ana = 15, bia = 50, *vez = 15 « **pp = 15 mudou a ana ana = 15, bia = 50, *vez = 50 « *pp = &bia: agora vez aponta para a bia ana = 15, bia = 55, *vez = 55 « o mesmo **pp = 55 agora muda a bia
| Linha | O que faz |
|---|---|
**pp = 15; | Segue pp até vez, segue vez até ana e guarda 15 lá. É o mesmo que *vez = 15; ou ana = 15;. |
*pp = &bia; | Segue pp só uma vez, até vez, e guarda nele o endereço de bia. É o mesmo que vez = &bia;. Ninguém mexeu em ana nem em bia. |
**pp = 55; | A mesma escrita da primeira linha, mas agora o caminho termina na bia. |
*pp = &bia troca a seta do meio. pp continua apontando para vez; quem mudou foi o conteúdo de vez.Experimente: rode e confira as três linhas. Depois acrescente, no fim, *pp = &ana; seguido de **pp += 1; e mostre de novo: quem ganhou o ponto? (No editor os endereços seriam bem menores, como 0x7ffff8, porque o programa roda em WebAssembly, dentro do navegador; por isso este exemplo nem mostra endereços.)
#include <stdio.h>
int main(void) {
int ana = 10;
int bia = 50;
int *vez = &ana;
int **pp = &vez;
**pp = 15;
printf("ana = %d, bia = %d, *vez = %d\n", ana, bia, *vez);
*pp = &bia;
printf("ana = %d, bia = %d, *vez = %d\n", ana, bia, *vez);
**pp = 55;
printf("ana = %d, bia = %d, *vez = %d\n", ana, bia, *vez);
return 0;
}
3. Para que serve: uma função que muda um ponteiro
Até aqui, pp só complicou o que vez fazia sozinho. O motivo de existir o ** aparece com funções. Lembre da regra de Passar por Ponteiro: em C, a função recebe uma cópia de cada argumento. Para uma função mudar um int de quem chamou, ela recebe um int *. Pela mesma lógica:
| A função precisa mudar… | …então recebe | …e quem chama passa |
|---|---|---|
um int | int * | &numero |
um int * (um ponteiro) | int ** | &ponteiro |
um char * (uma string) | char ** | &texto |
A versão que não funciona
A Ana quer uma função criar_placar que reserve memória para um placar e deixe o ponteiro do main apontando para ela. Para reservar memória, o programa usa malloc ("memory allocation", reservar memória), que devolve o endereço da memória nova; a aula malloc e free explica tudo com calma. Aqui só importa: malloc devolve um endereço, e alguém precisa guardá-lo.
#include <stdio.h>
#include <stdlib.h>
void criar_placar(int *placar) {
placar = malloc(sizeof *placar);
if (placar == NULL) return;
*placar = 0;
printf("Dentro da função: placar = %p\n", (void *)placar);
}
int main(void) {
int *placar = NULL;
criar_placar(placar);
printf("De volta no main: placar = %p\n", (void *)placar);
if (placar == NULL) {
printf("O main continua sem placar!\n");
return 1;
}
free(placar);
return 0;
}
$ gcc -std=c23 -Wall -Wextra aloca1.c -o aloca1 $ ./aloca1 Dentro da função: placar = 0x59ee8dd79010 De volta no main: placar = (nil) « (nil) é como a glibc mostra o NULL O main continua sem placar!
Compilou sem nenhum aviso, e mesmo assim não funcionou. O que aconteceu:
- No
main,placarvaleNULL. A chamadacriar_placar(placar)entrega uma cópia desse valor ao parâmetro, que também se chamaplacarmas é outra variável, que só existe dentro da função. placar = malloc(…)guarda o endereço novo na cópia. Oplacardomainnem fica sabendo.- A função termina, a cópia desaparece, e o endereço da memória reservada se perde para sempre: além de não funcionar, o programa vazou memória (aula Vazamentos de Memória).
É o mesmo erro de escrever void dobrar(int n) { n = n * 2; } e esperar que o número de quem chamou dobre. O fato de a cópia ser um ponteiro não muda nada: mudar a cópia não muda o original. (Mudar o lugar para onde ela aponta, com *placar = …, funcionaria, mas aqui o que queremos mudar é o próprio ponteiro.)
A versão que funciona: int **
#include <stdio.h>
#include <stdlib.h>
void criar_placar(int **placar) {
*placar = malloc(sizeof **placar);
if (*placar == NULL) return;
**placar = 0;
printf("Dentro da função: *placar = %p\n", (void *)*placar);
}
int main(void) {
int *placar = NULL;
criar_placar(&placar);
printf("De volta no main: placar = %p\n", (void *)placar);
if (placar == NULL) {
printf("O main continua sem placar!\n");
return 1;
}
*placar += 3;
printf("Placar: %d\n", *placar);
free(placar);
return 0;
}
- void criar_placar(int **placar)
- O parâmetro agora é o endereço do ponteiro do
main. Ele ainda é uma cópia, mas uma cópia do endereço, e com o endereço dá para ir até o original. - *placar = malloc(…);
- Um salto: guarda o endereço novo no ponteiro do
main. - sizeof **placar
- O tamanho de um
int(o que fica no fim dos dois saltos).sizeofnão segue ponteiro nenhum de verdade; ele só olha o tipo, então funciona mesmo antes de existir memória. - **placar = 0;
- Dois saltos: zera o número dentro da memória nova.
- criar_placar(&placar);
- O
mainpassa o endereço do seu ponteiro. É o&que dá à função o poder de mudá-lo.
$ gcc -std=c23 -Wall -Wextra aloca2.c -o aloca2 $ ./aloca2 Dentro da função: *placar = 0x5e1a462c4010 De volta no main: placar = 0x5e1a462c4010 « agora o main recebeu o endereço Placar: 3
int **, a função alcança o ponteiro do main e guarda nele o endereço da memória nova. Quando a função termina e o parâmetro some, o main continua com o endereço.Missão: este é o aloca1.c, que não funciona. Conserte-o com um ponteiro para ponteiro: mude o parâmetro para int **placar, ajuste os asteriscos dentro da função e passe &placar na chamada. A saída tem de terminar com Placar: 3.
#include <stdio.h>
#include <stdlib.h>
void criar_placar(int *placar) {
placar = malloc(sizeof *placar);
if (placar == NULL) return;
*placar = 0;
}
int main(void) {
int *placar = NULL;
criar_placar(placar);
if (placar == NULL) {
printf("O main continua sem placar!\n");
return 1;
}
*placar += 3;
printf("Placar: %d\n", *placar);
free(placar);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
void criar_placar(int **placar) {
*placar = malloc(sizeof **placar);
if (*placar == NULL) return;
**placar = 0;
}
int main(void) {
int *placar = NULL;
criar_placar(&placar);
if (placar == NULL) {
printf("O main continua sem placar!\n");
return 1;
}
*placar += 3;
printf("Placar: %d\n", *placar);
free(placar);
return 0;
}
A outra saída: devolver o ponteiro
Muitas vezes dá para fugir do ** fazendo a função devolver o endereço com return:
#include <stdio.h>
#include <stdlib.h>
int *criar_placar(void) {
int *placar = malloc(sizeof *placar);
if (placar != NULL) {
*placar = 0;
}
return placar; // devolve o endereço (ou NULL, se faltou memória)
}
int main(void) {
int *placar = criar_placar();
if (placar == NULL) {
printf("Faltou memória!\n");
return 1;
}
*placar += 3;
printf("Placar: %d\n", *placar);
free(placar);
return 0;
}
$ gcc -std=c23 -Wall -Wextra aloca3.c -o aloca3 $ ./aloca3 Placar: 3
É o que o próprio malloc faz. Então, qual usar?
| Situação | Prefira |
|---|---|
| A função cria uma coisa e não tem mais nada a devolver | Devolver o ponteiro: int *criar(void). É mais simples de ler e de usar. |
O return já está ocupado (por exemplo, com um código de erro: 0 = deu certo, -1 = falhou) | int **: int criar(int **saida). |
| A função precisa mudar dois ou mais ponteiros | ** para cada um (é o caso da missão final). |
| A função precisa trocar um ponteiro que já aponta para algo (andar numa lista, trocar o começo de uma lista ligada) | **. |
strtol
A função strtol ("string to long", texto para número) tem o protótipo long strtol(const char *texto, char **fim, int base). O return já devolve o número; o char **fim é por onde ela conta onde parou de ler: ela muda um ponteiro seu (char *fim; … strtol(texto, &fim, 10)). Veja em Texto ↔ Número.
4. Listas de strings: char ** e o argv
O outro lugar onde o ** aparece o tempo todo é em listas de textos. Uma string em C é acessada por um char *, o endereço da primeira letra (aula Ponteiros e Strings). Uma lista de strings é então um array de char *, e o nome de um array vira o endereço do primeiro item (aula Ponteiros e Arrays). O endereço de um char * é um char **.
#include <stdio.h>
void listar(const char **lista) {
for (const char **p = lista; *p != NULL; p++) {
printf("- %s (começa com %c)\n", *p, **p);
}
}
int main(void) {
const char *compras[] = {"arroz", "feijão", "café", NULL};
listar(compras);
return 0;
}
- const char *compras[]
- Um array de ponteiros para texto. Cada item guarda o endereço de uma string; as letras moram em outro lugar. O
constdiz que não vamos mudar as letras (aula const e Ponteiros). - NULL
- Um último item "vazio" marca o fim da lista, assim quem percorre não precisa saber o tamanho. (O
argvtambém termina com umNULL.) - listar(compras)
- O array vira o endereço do primeiro item,
&compras[0], que é umconst char **. - const char **p = lista
panda pelos itens da lista, um por vez (p++pula um ponteiro inteiro, 8 bytes no laboratório).- *p
- Um salto: o item atual, um
const char *, que o%smostra como texto. - **p
- Dois saltos: a primeira letra do item atual, um
char, mostrada pelo%c.
$ gcc -std=c23 -Wall -Wextra frutas.c -o frutas $ ./frutas - arroz (começa com a) - feijão (começa com f) - café (começa com c)
p aponta para um item do array; cada item aponta para as letras. *p é o item (o texto inteiro) e **p é a primeira letra dele. Quando *p vale NULL, a lista acabou.O argv do main
O main pode receber o que você digita depois do nome do programa no terminal. A forma completa dele é int main(int argc, char **argv), e agora você sabe ler esse tipo: argv aponta para o primeiro item de uma lista de strings, exatamente como o p acima. (Muita gente escreve char *argv[]; num parâmetro de função, as duas escritas são a mesma coisa.)
#include <stdio.h>
int main(int argc, char **argv) {
for (int i = 0; i < argc; i++) {
printf("argv[%d] = %s\n", i, argv[i]);
}
return 0;
}
$ gcc -std=c23 -Wall -Wextra args.c -o args $ ./args Ana Bia argv[0] = ./args « o primeiro item é o nome do programa argv[1] = Ana argv[2] = Bia
O argc ("argument count") conta os itens; o argv ("argument vector", lista de argumentos) é a lista. Os detalhes estão em Argumentos da Linha de Comando.
int ***?
Existe: um ponteiro para ponteiro para ponteiro, com três saltos. Aparece, por exemplo, numa função que precisa criar uma lista de strings para quem chamou (ela recebe um char ***). Mas é raro; quando você sentir vontade de escrever três asteriscos, quase sempre uma struct deixa o código mais claro.
Quando dá errado
error: passing argument 1 of ‘criar_placar’ from incompatible pointer type
Você esqueceu o & na chamada: a função pede um int ** e recebeu um int *.
$ gcc -std=c23 -Wall -Wextra erro1.c -o erro1
erro1.c: In function ‘main’:
erro1.c:13:18: error: passing argument 1 of ‘criar_placar’ from incompatible pointer type [-Wincompatible-pointer-types]
13 | criar_placar(placar);
| ^~~~~~
| |
| int *
erro1.c:4:25: note: expected ‘int **’ but argument is of type ‘int *’
4 | void criar_placar(int **placar) {
| ~~~~~~^~~~~~
O que significa: "passando o argumento 1 de criar_placar a partir de um tipo de ponteiro incompatível". A nota diz tudo: expected ‘int **’ but argument is of type ‘int *’ = "esperava int **, mas o argumento é int *". Até o gcc 13 isso era só um aviso (e o programa quebrava ao rodar); desde o gcc 14 é erro. O Clang do editor diz incompatible pointer types passing 'int *' to parameter of type 'int **'; take the address with & e até sugere o conserto: "pegue o endereço com &".
Como consertar: criar_placar(&placar);. Conte os asteriscos: o tipo do argumento tem de ter o mesmo número de * que o parâmetro.
error: initialization of ‘int **’ from incompatible pointer type ‘int *’
Você escreveu int **pp = &moedas;, pulando o ponteiro do meio:
$ gcc -std=c23 -Wall -Wextra erro2.c -o erro2
erro2.c: In function ‘main’:
erro2.c:5:16: error: initialization of ‘int **’ from incompatible pointer type ‘int *’ [-Wincompatible-pointer-types]
5 | int **pp = &moedas;
| ^
O que significa: &moedas é um int * (um & põe só um asterisco). Um int ** precisa do endereço de um ponteiro.
Como consertar: crie o ponteiro do meio (int *p = &moedas;) e aponte pp para ele: int **pp = &p;.
warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘int *’
Você usou um asterisco a menos: printf("%d\n", *pp);.
$ gcc -std=c23 -Wall -Wextra erro3.c -o erro3
erro3.c: In function ‘main’:
erro3.c:7:14: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘int *’ [-Wformat=]
7 | printf("%d\n", *pp);
| ~^ ~~~
| | |
| int int *
| %ls
$ ./erro3
-34380812 « um pedaço do endereço, mostrado como número
O que significa: *pp ainda é um ponteiro (int *), não o número. O programa é gerado, mas mostra um pedaço do endereço como se fosse número (e outro a cada execução). (A sugestão %ls do gcc não tem nada a ver com o seu caso: ela serve para textos "largos". Ignore.)
Como consertar: para chegar no int, são dois saltos: **pp.
Falha de segmentação (imagem do núcleo gravada) ao usar **pp
#include <stdio.h>
int main(void) {
int *p = NULL;
int **pp = &p;
printf("Vou escrever em **pp...\n");
**pp = 5;
printf("Escrevi!\n");
return 0;
}
$ gcc -std=c23 -Wall -Wextra nulo.c -o nulo $ ./nulo Vou escrever em **pp... Falha de segmentação (imagem do núcleo gravada) ./nulo
O que significa: o primeiro salto deu certo (pp aponta para p, que existe), mas p vale NULL, e o segundo salto tentou escrever no endereço zero. O sistema matou o programa (veja Ponteiro Nulo). No editor do guia acontece o mesmo: Falha de segmentação (imagem do núcleo gravada) e código de saída 139.
Como consertar: confira os dois níveis antes de usar: if (pp != NULL && *pp != NULL) **pp = 5;. Numa função que recebe int **, o pp quase sempre é válido (veio de um &); quem costuma ser NULL é o *pp.
main não mudou
O que significa: é o aloca1.c da seção 3. A função recebe int * e faz p = …: muda só a cópia. Nenhum compilador avisa, porque o código é válido; ele só não faz o que você queria.
Como consertar: receba int **, escreva *p = … lá dentro e chame com &. Ou devolva o ponteiro com return.
Referência rápida
| Escrita | Tipo | O que é |
|---|---|---|
int **pp = &p; | int ** | Declara um ponteiro para ponteiro que aponta para o ponteiro p |
pp | int ** | O endereço de p |
*pp | int * | O próprio p (o endereço guardado nele) |
**pp | int | A variável no fim dos dois saltos |
*pp = &outra; | Faz p apontar para outra | |
**pp = 7; | Guarda 7 no fim dos dois saltos | |
void f(int **saida) + f(&p); | Função que pode mudar o ponteiro p de quem chama | |
int main(int argc, char **argv) | char ** | Lista de strings com os argumentos (termina em NULL) |
const char **p = lista; … p++ | const char ** | Anda por uma lista de strings; *p é o texto, **p a primeira letra |
Exercícios
1. Declarar
Complete para criar pp, que aponta para o ponteiro p (que é um double *):
2. Dois saltos
Com int x = 3; int *p = &x; int **pp = &p;, complete para guardar 9 em x usando só pp:
3. Chamar a função
A função é void abrir(char **nome) e você tem char *arquivo = NULL;. Complete a chamada para que abrir possa mudar arquivo:
4. Com int x = 3; int *p = &x; int **pp = &p;, qual é o tipo de *pp?
5. Depois de int a = 1, b = 2; int *p = &a; int **pp = &p; *pp = &b; **pp = 9;, quanto valem a e b?
6. Por que void criar(int *p) { p = malloc(sizeof *p); } não entrega a memória para quem chamou?
Missão final: trocar os ponteiros, não os valores
Missão: no pódio do jogo da Ana, primeiro aponta para ouro e segundo para prata. Escreva o corpo de trocar para que os dois ponteiros troquem de alvo, sem mexer nos números guardados em ouro e prata. Dica: guarde *a num int *temp, como na troca de dois números. A saída deve ser, sem avisos:
primeiro aponta para 50 segundo aponta para 100 ouro = 100, prata = 50
#include <stdio.h>
void trocar(int **a, int **b) {
// troque para onde *a e *b apontam
}
int main(void) {
int ouro = 100;
int prata = 50;
int *primeiro = &ouro;
int *segundo = &prata;
trocar(&primeiro, &segundo);
printf("primeiro aponta para %d\n", *primeiro);
printf("segundo aponta para %d\n", *segundo);
printf("ouro = %d, prata = %d\n", ouro, prata);
return 0;
}
#include <stdio.h>
void trocar(int **a, int **b) {
int *temp = *a;
*a = *b;
*b = temp;
}
int main(void) {
int ouro = 100;
int prata = 50;
int *primeiro = &ouro;
int *segundo = &prata;
trocar(&primeiro, &segundo);
printf("primeiro aponta para %d\n", *primeiro);
printf("segundo aponta para %d\n", *segundo);
printf("ouro = %d, prata = %d\n", ouro, prata);
return 0;
}
Repare no tipo do temp: ele guarda um *a, que é um int *. Se você escrever int temp = *a;, o gcc recusa com error: initialization of ‘int’ from ‘int *’ makes integer from pointer without a cast ("transforma ponteiro em inteiro sem conversão"). Trocar ponteiros em vez de valores é muito usado para ordenar coisas grandes: em vez de copiar fichas inteiras de um lado para o outro, você troca só as setas (veja Ordenação).
Resumo
- Um ponteiro também mora num endereço; quem guarda esse endereço é um ponteiro para ponteiro:
int **pp = &p;. ppé o endereço dep;*ppé o própriop;**ppé o valor final. Cada*na frente tira um*do tipo; cada&põe um.*pp = &outra;muda para onde o ponteiro do meio aponta;**pp = 7;muda o valor final.- Para uma função mudar um ponteiro de quem chama (por exemplo, guardar nele a memória de um
malloc), ela recebetipo **e é chamada com&ponteiro. Com umtipo *ela só mudaria a cópia. - Quando dá, devolver o ponteiro com
returné mais simples; o**fica para quando oreturnestá ocupado ou há vários ponteiros a mudar (como nostrtol). - Listas de strings são arrays de
char *; quem anda nelas é umchar **, como oargvdomain. - Erros típicos: esquecer o
&(incompatible pointer type, erro desde o gcc 14), um*a menos noprintfe**ppcom*ppvalendoNULL(falha de segmentação).