C goto
O goto ("vá para") é o comando mais simples e mais polêmico do C: ele faz o programa pular para outra linha da mesma função, marcada com um nome. Em 1968, o cientista Edsger Dijkstra escreveu uma carta famosa, "Go To Statement Considered Harmful" ("o goto é considerado nocivo"), e desde então muita gente ensina que ele nunca deve ser usado. Mesmo assim, ele aparece milhares de vezes no código do Linux. Nesta aula você vai ver como ele funciona, por que ele tem má fama e qual é o uso em que ele é a melhor ferramenta: a limpeza no fim de uma função.
- A escrever um rótulo e um
goto. - Por que pular para trás é má ideia, e o que usar no lugar.
- O uso aceito: o bloco de limpeza, que fecha arquivos e libera memória em todos os caminhos de erro (conferido com o
valgrind). - As regras: o
gotonão sai da função, não entra no escopo de um VLA e pode pular uma inicialização. - O rótulo no fim do bloco C23 e o
breakcom nome C2y. - Os erros reais:
label used but not defined,duplicate label,jump into scope of identifier with variably modified type.
Você já viu um goto na aula Laços Aninhados, para sair de dois laços de uma vez. Aqui está o resto da história. Os comandos rodaram na máquina fictícia da Ana, na pasta ~/c/goto, com o gcc 15.2.
A ideia: o mapa do tesouro
Pense num livro de aventura em que cada página termina com "se você abrir a porta, vá para a página 47; se fugir, vá para a página 112". O leitor pula para frente e para trás. Com dez páginas, é divertido. Com mil, ninguém mais sabe por onde passou nem como chegou numa página: é preciso voltar o livro inteiro para descobrir.
O goto é o "vá para a página 47". O if, o for, o while e as funções são pulos organizados: você olha para o código e vê o começo e o fim de cada bloco. O goto é um pulo livre, e é aí que mora o perigo. A regra prática deste guia:
- Pule só para a frente, nunca para trás.
- Use-o só para sair: de vários laços de uma vez, ou de uma função com erro, passando pela limpeza.
1. Rótulo e goto
A Ana procura um produto nas prateleiras da lojinha. Quando acha, quer parar a busca inteira, e não só o laço de dentro:
#include <stdio.h>
#include <string.h>
int main(void) {
const char *prateleiras[3][4] = {
{"arroz", "feijão", "açúcar", "sal"},
{"caderno", "lápis", "borracha", "régua"},
{"sabão", "esponja", "vassoura", "balde"},
};
const char *procurado = "borracha";
for (int p = 0; p < 3; p++) {
for (int i = 0; i < 4; i++) {
if (strcmp(prateleiras[p][i], procurado) == 0) {
printf("%s: prateleira %d, posição %d\n", procurado, p + 1, i + 1);
goto fim;
}
}
}
printf("%s: não temos\n", procurado);
fim:
printf("Busca encerrada.\n");
return 0;
}
- fim:
- Um rótulo (label): um nome, com as mesmas regras dos nomes de variáveis, seguido de dois-pontos. Ele não faz nada sozinho; só marca a linha. Costuma ficar encostado na margem esquerda, para chamar a atenção.
- goto fim;
- "Vá para o rótulo
fim": o programa salta para lá na hora, saindo dos doisfor. Oprintf("… não temos")fica para trás. - printf("%s: não temos\n", …)
- Só roda se os laços terminarem sem achar. Depois dele, o programa segue normalmente e também passa pelo
fim:.
$ gcc -std=c23 -Wall -Wextra prateleira.c -o prateleira $ ./prateleira borracha: prateleira 2, posição 3 Busca encerrada.
Com procurado = "pão", os laços vão até o fim, e o programa passa pelas duas linhas finais:
$ gcc -std=c23 -Wall -Wextra prateleira.c -o prateleira && ./prateleira pão: não temos Busca encerrada.
Missão: rode e depois troque o procurado por "vassoura" e por "pão". Por fim, provoque o erro mais comum: escreva o rótulo errado no goto (por exemplo, goto fin;) e rode. O Clang responde use of undeclared label 'fin' ("uso de rótulo não declarado"); o gcc, label ‘fin’ used but not defined.
#include <stdio.h>
#include <string.h>
int main(void) {
const char *prateleiras[3][4] = {
{"arroz", "feijão", "açúcar", "sal"},
{"caderno", "lápis", "borracha", "régua"},
{"sabão", "esponja", "vassoura", "balde"},
};
const char *procurado = "borracha";
for (int p = 0; p < 3; p++) {
for (int i = 0; i < 4; i++) {
if (strcmp(prateleiras[p][i], procurado) == 0) {
printf("%s: prateleira %d, posição %d\n", procurado, p + 1, i + 1);
goto fim;
}
}
}
printf("%s: não temos\n", procurado);
fim:
printf("Busca encerrada.\n");
return 0;
}
#include <stdio.h>
#include <string.h>
int main(void) {
const char *prateleiras[3][4] = {
{"arroz", "feijão", "açúcar", "sal"},
{"caderno", "lápis", "borracha", "régua"},
{"sabão", "esponja", "vassoura", "balde"},
};
const char *procurado = "borracha";
for (int p = 0; p < 3; p++) {
for (int i = 0; i < 4; i++) {
if (strcmp(prateleiras[p][i], procurado) == 0) {
printf("%s: prateleira %d, posição %d\n", procurado, p + 1, i + 1);
goto fin;
}
}
}
printf("%s: não temos\n", procurado);
fim:
printf("Busca encerrada.\n");
return 0;
}
2. Pular para trás: dá, mas não faça
O goto também pula para cima. Com isso dá para fazer uma repetição sem nenhum laço. Este programa pede o código de um produto até receber um entre 1 e 99:
#include <stdio.h>
int main(void) {
int tentativas = 0;
int codigo = 0;
de_novo:
tentativas++;
printf("Código do produto (1 a 99): ");
if (scanf("%d", &codigo) != 1) {
return 1;
}
if (codigo < 1 || codigo > 99) {
printf("Inválido.\n");
goto de_novo;
}
printf("Produto %d, na tentativa %d.\n", codigo, tentativas);
return 0;
}
$ gcc -std=c23 -Wall -Wextra codigo.c -o codigo $ ./codigo Código do produto (1 a 99): 150 Inválido. Código do produto (1 a 99): 0 Inválido. Código do produto (1 a 99): 42 Produto 42, na tentativa 3.
Funciona. Mas, para descobrir que há uma repetição ali, você tem de achar o goto de_novo lá embaixo e subir procurando o rótulo. Num programa maior, com dois ou três rótulos assim, ninguém mais consegue seguir o caminho: é o "código espaguete". O C tem um laço feito exatamente para "faça, e repita se não deu certo", o do while:
#include <stdio.h>
int main(void) {
int tentativas = 0;
int codigo = 0;
do {
tentativas++;
printf("Código do produto (1 a 99): ");
if (scanf("%d", &codigo) != 1) {
return 1;
}
if (codigo < 1 || codigo > 99) {
printf("Inválido.\n");
}
} while (codigo < 1 || codigo > 99);
printf("Produto %d, na tentativa %d.\n", codigo, tentativas);
return 0;
}
$ gcc -std=c23 -Wall -Wextra codigo2.c -o codigo2 $ ./codigo2 Código do produto (1 a 99): 150 Inválido. Código do produto (1 a 99): 0 Inválido. Código do produto (1 a 99): 42 Produto 42, na tentativa 3.
A mesma saída, e agora o do { avisa logo na primeira linha: "isto se repete", e o } mostra onde a repetição termina. Todo goto para trás pode virar um laço, e o laço é sempre mais fácil de ler.
3. O uso aceito: o bloco de limpeza
Uma função que pega recursos (abre um arquivo, reserva memória) tem de devolvê-los em todos os caminhos: no caminho feliz e em cada erro. Veja a função que lê os preços de um arquivo e mostra o total. Ela tem três jeitos de dar errado: o arquivo não abre, a memória não vem, ou o arquivo não tem nenhum preço.
#include <stdio.h>
#include <stdlib.h>
int relatorio(const char *nome) {
FILE *f = fopen(nome, "r");
if (f == nullptr) {
perror(nome);
return -1;
}
int *precos = malloc(100 * sizeof *precos);
if (precos == nullptr) {
printf("Sem memória\n");
fclose(f);
return -1;
}
int n = 0;
while (n < 100 && fscanf(f, "%d", &precos[n]) == 1) {
n++;
}
if (n == 0) {
printf("%s: nenhum preço\n", nome);
return -1;
}
int total = 0;
for (int i = 0; i < n; i++) {
total += precos[i];
}
printf("%s: %d preços, total R$ %d\n", nome, n, total);
free(precos);
fclose(f);
return 0;
}
int main(void) {
relatorio("precos.txt");
relatorio("vazio.txt");
relatorio("sumiu.txt");
return 0;
}
O precos.txt tem 12 3 25 8; o vazio.txt está vazio; o sumiu.txt não existe. A saída parece certa:
$ gcc -std=c23 -Wall -Wextra -g vaza.c -o vaza $ ./vaza precos.txt: 4 preços, total R$ 48 vazio.txt: nenhum preço sumiu.txt: No such file or directory
Mas o return -1 do "nenhum preço" esqueceu o free e o fclose. O valgrind mostra:
$ valgrind --leak-check=full ./vaza ==445219== Memcheck, a memory error detector ==445219== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward et al. ==445219== Using Valgrind-3.26.0 and LibVEX; rerun with -h for copyright info ==445219== Command: ./vaza ==445219== precos.txt: 4 preços, total R$ 48 vazio.txt: nenhum preço sumiu.txt: No such file or directory ==445219== ==445219== HEAP SUMMARY: ==445219== in use at exit: 872 bytes in 2 blocks ==445219== total heap usage: 12 allocs, 10 frees, 12,950 bytes allocated ==445219== ==445219== 400 bytes in 1 blocks are definitely lost in loss record 1 of 2 ==445219== at 0x4850858: malloc (vg_replace_malloc.c:447) ==445219== by 0x4001279: relatorio (vaza.c:11) ==445219== by 0x40013B0: main (vaza.c:40) ==445219== ==445219== LEAK SUMMARY: ==445219== definitely lost: 400 bytes in 1 blocks ==445219== indirectly lost: 0 bytes in 0 blocks ==445219== possibly lost: 0 bytes in 0 blocks ==445219== still reachable: 472 bytes in 1 blocks ==445219== suppressed: 0 bytes in 0 blocks ==445219== Reachable blocks (those to which a pointer was found) are not shown. ==445219== To see them, rerun with: --leak-check=full --show-leak-kinds=all ==445219== ==445219== For lists of detected and suppressed errors, rerun with: -s ==445219== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
Os 400 bytes perdidos são os 100 int do malloc da linha 11; os 472 bytes "ainda alcançáveis" são o FILE do vazio.txt, que nunca foi fechado. Cada return no meio da função é um lugar onde a limpeza pode ser esquecida, e quanto mais recursos, mais cópias da limpeza você tem de manter em dia.
Uma saída só, com goto
O padrão usado no Linux, no Git e em muitos outros projetos em C: a limpeza fica uma vez só, no fim da função, em rótulos na ordem inversa da reserva. Cada erro pula para o rótulo que desfaz só o que já foi feito:
#include <stdio.h>
#include <stdlib.h>
int relatorio(const char *nome) {
int resultado = -1;
int *precos = nullptr;
FILE *f = fopen(nome, "r");
if (f == nullptr) {
perror(nome);
goto fim;
}
precos = malloc(100 * sizeof *precos);
if (precos == nullptr) {
printf("Sem memória\n");
goto fecha_arquivo;
}
int n = 0;
while (n < 100 && fscanf(f, "%d", &precos[n]) == 1) {
n++;
}
if (n == 0) {
printf("%s: nenhum preço\n", nome);
goto libera_precos;
}
int total = 0;
for (int i = 0; i < n; i++) {
total += precos[i];
}
printf("%s: %d preços, total R$ %d\n", nome, n, total);
resultado = 0;
libera_precos:
free(precos);
fecha_arquivo:
fclose(f);
fim:
return resultado;
}
int main(void) {
relatorio("precos.txt");
relatorio("vazio.txt");
relatorio("sumiu.txt");
return 0;
}
- int resultado = -1;
- Começa como "deu errado". Só no fim do caminho feliz vira
0. Assim, todogotode erro já sai com o valor certo. - int *precos = nullptr;
- Declarado no começo, antes do primeiro
goto, para nenhum pulo passar por cima dele (veja a seção 4). - goto fim;
- O arquivo nem abriu: não há nada para desfazer.
- goto fecha_arquivo;
- O arquivo abriu, a memória não: só fecha o arquivo.
- goto libera_precos;
- Os dois deram certo: libera a memória e fecha o arquivo.
- libera_precos: … fecha_arquivo: … fim:
- Os rótulos ficam em escada, na ordem inversa da reserva (o último reservado é o primeiro devolvido). O caminho feliz também desce por eles: depois do
resultado = 0;, o programa continua e passa pelos três.
$ gcc -std=c23 -Wall -Wextra -g limpeza.c -o limpeza $ ./limpeza precos.txt: 4 preços, total R$ 48 vazio.txt: nenhum preço sumiu.txt: No such file or directory $ valgrind --leak-check=full ./limpeza ==445234== Memcheck, a memory error detector ==445234== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward et al. ==445234== Using Valgrind-3.26.0 and LibVEX; rerun with -h for copyright info ==445234== Command: ./limpeza ==445234== precos.txt: 4 preços, total R$ 48 vazio.txt: nenhum preço sumiu.txt: No such file or directory ==445234== ==445234== HEAP SUMMARY: ==445234== in use at exit: 0 bytes in 0 blocks ==445234== total heap usage: 12 allocs, 12 frees, 12,950 bytes allocated ==445234== ==445234== All heap blocks were freed -- no leaks are possible ==445234== ==445234== For lists of detected and suppressed errors, rerun with: -s ==445234== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
"Todos os blocos foram liberados: nenhum vazamento é possível." Acrescentar um quarto erro amanhã é uma linha: goto libera_precos;. A limpeza não é copiada de novo.
E sem goto?
Dá para escrever a mesma função só com if, um dentro do outro. O escada.c abaixo (com os mesmos #include e o mesmo main) dá a mesma saída:
int relatorio(const char *nome) {
int resultado = -1;
FILE *f = fopen(nome, "r");
if (f != nullptr) {
int *precos = malloc(100 * sizeof *precos);
if (precos != nullptr) {
int n = 0;
while (n < 100 && fscanf(f, "%d", &precos[n]) == 1) {
n++;
}
if (n > 0) {
int total = 0;
for (int i = 0; i < n; i++) {
total += precos[i];
}
printf("%s: %d preços, total R$ %d\n", nome, n, total);
resultado = 0;
} else {
printf("%s: nenhum preço\n", nome);
}
free(precos);
} else {
printf("Sem memória\n");
}
fclose(f);
} else {
perror(nome);
}
return resultado;
}
$ gcc -std=c23 -Wall -Wextra escada.c -o escada $ ./escada precos.txt: 4 preços, total R$ 48 vazio.txt: nenhum preço sumiu.txt: No such file or directory
Também está certo, e não usa goto. Mas cada recurso empurra o código um nível para a direita, e a mensagem de cada erro fica longe do teste que a causou (o perror do fopen está lá no fim). Com dois recursos, as duas formas se equivalem; com quatro ou cinco, a escada fica difícil de ler, e o bloco de limpeza ganha. Escolha a que ficar mais clara, e use o valgrind para conferir as duas.
Experimente: o mesmo limpeza.c, com os arquivos nas abas. Ponha um printf(" (liberei os preços)\n"); depois do free e um printf(" (fechei %s)\n", nome); depois do fclose, e rode: veja por quais rótulos cada chamada passa. Depois apague o conteúdo da aba precos.txt.
#include <stdio.h>
#include <stdlib.h>
int relatorio(const char *nome) {
int resultado = -1;
int *precos = nullptr;
FILE *f = fopen(nome, "r");
if (f == nullptr) {
perror(nome);
goto fim;
}
precos = malloc(100 * sizeof *precos);
if (precos == nullptr) {
printf("Sem memória\n");
goto fecha_arquivo;
}
int n = 0;
while (n < 100 && fscanf(f, "%d", &precos[n]) == 1) {
n++;
}
if (n == 0) {
printf("%s: nenhum preço\n", nome);
goto libera_precos;
}
int total = 0;
for (int i = 0; i < n; i++) {
total += precos[i];
}
printf("%s: %d preços, total R$ %d\n", nome, n, total);
resultado = 0;
libera_precos:
free(precos);
fecha_arquivo:
fclose(f);
fim:
return resultado;
}
int main(void) {
relatorio("precos.txt");
relatorio("vazio.txt");
relatorio("sumiu.txt");
return 0;
}
12 3 25 8
4. As regras do goto
Só dentro da mesma função
Um rótulo vale na função inteira em que está (antes ou depois do goto, em qualquer bloco), e só nela. Não dá para pular de uma função para outra:
#include <stdio.h>
void procurar(void) {
goto fim;
}
int main(void) {
procurar();
fim:
printf("Fim\n");
return 0;
}
$ gcc -std=c23 -Wall -Wextra outrafunc.c -o outrafunc
outrafunc.c: In function ‘procurar’:
outrafunc.c:4:5: error: label ‘fim’ used but not defined
4 | goto fim;
| ^~~~
outrafunc.c: In function ‘main’:
outrafunc.c:9:1: warning: label ‘fim’ defined but not used [-Wunused-label]
9 | fim:
| ^~~
Para o procurar, o fim não existe ("rótulo usado, mas não definido"); para o main, ele existe e ninguém o usa ("definido, mas não usado"). Para sair de uma função, use return. Para voltar de várias funções de uma vez existe o longjmp, assunto da próxima aula, setjmp e longjmp.
Um nome, um rótulo
#include <stdio.h>
int main(void) {
goto fim;
fim:
printf("um\n");
fim:
printf("dois\n");
return 0;
}
$ gcc -std=c23 -Wall -Wextra dupl.c -o dupl
dupl.c: In function ‘main’:
dupl.c:7:1: error: duplicate label ‘fim’
7 | fim:
| ^~~
dupl.c:5:1: note: previous definition of ‘fim’ with type ‘void’
5 | fim:
| ^~~
"Rótulo duplicado". O Clang, no editor, diz redefinition of label 'fim'. Como o rótulo vale na função inteira, nem em blocos diferentes dá para repetir o nome. Em outra função, pode.
Não pule para dentro de um VLA
Um array de tamanho variável (VLA, aula Arrays) é criado quando o programa passa pela declaração. Pular para depois dela, dentro do bloco, deixaria o array sem existir:
#include <stdio.h>
int main(void) {
int n = 3;
goto dentro;
{
int notas[n];
dentro:
notas[0] = 10;
printf("%d\n", notas[0]);
}
return 0;
}
$ gcc -std=c23 -Wall -Wextra vla.c -o vla
vla.c: In function ‘main’:
vla.c:5:5: error: jump into scope of identifier with variably modified type
5 | goto dentro;
| ^~~~
vla.c:8:5: note: label ‘dentro’ defined here
8 | dentro:
| ^~~~~~
vla.c:7:13: note: ‘notas’ declared here
7 | int notas[n];
| ^~~~~
"Salto para dentro do escopo de um identificador com tipo variável." O Clang diz cannot jump from this goto statement to its label e jump bypasses initialization of variable length array ("o salto passa por cima da criação do array de tamanho variável").
Cuidado: pular por cima de uma inicialização
Com uma variável comum, o C deixa pular por cima da declaração. A variável existe, mas o valor inicial nunca é guardado:
#include <stdio.h>
int main(void) {
goto mostrar;
int estoque = 12;
mostrar:
printf("Estoque: %d\n", estoque);
return 0;
}
$ gcc -std=c23 -Wall -Wextra pulainit.c -o pulainit && ./pulainit
pulainit.c: In function ‘main’:
pulainit.c:7:5: warning: ‘estoque’ is used uninitialized [-Wuninitialized]
7 | printf("Estoque: %d\n", estoque);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
pulainit.c:5:9: note: ‘estoque’ was declared here
5 | int estoque = 12;
| ^~~~~~~
Estoque: 0
Compilou (é só um aviso), e o estoque saiu 0, não 12: o valor é lixo, que desta vez foi zero. Aqui o gcc percebeu porque o caso é simples. Para ele avisar sobre todo pulo por cima de uma inicialização, existe a opção -Wjump-misses-init, que não vem no -Wall:
$ gcc -std=c23 -Wall -Wextra -Wjump-misses-init pulainit.c -o pulainit
pulainit.c: In function ‘main’:
pulainit.c:4:5: warning: jump skips variable initialization [-Wjump-misses-init]
4 | goto mostrar;
| ^~~~
pulainit.c:6:1: note: label ‘mostrar’ defined here
6 | mostrar:
| ^~~~~~~
pulainit.c:5:9: note: ‘estoque’ declared here
5 | int estoque = 12;
| ^~~~~~~
pulainit.c:7:5: warning: ‘estoque’ is used uninitialized [-Wuninitialized]
7 | printf("Estoque: %d\n", estoque);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
pulainit.c:5:9: note: ‘estoque’ was declared here
5 | int estoque = 12;
| ^~~~~~~
"O salto pula a inicialização da variável." É por isso que, no limpeza.c, as variáveis que a limpeza usa (resultado, precos, f) são declaradas antes do primeiro goto. (Em C++, este programa nem compila; em C, é responsabilidade sua.)
O rótulo no fim do bloco C23
Até o C17, um rótulo tinha de vir antes de um comando. No fim de um bloco, logo antes do }, era preciso escrever um comando vazio (proximo: ;). O C23 tirou essa exigência:
#include <stdio.h>
int main(void) {
for (int i = 0; i < 3; i++) {
if (i == 1) {
goto proximo;
}
printf("%d\n", i);
proximo:
}
return 0;
}
$ gcc -std=c23 -Wall -Wextra finalbloco.c -o finalbloco && ./finalbloco
0
2
$ gcc -std=c17 -Wall -Wextra -pedantic finalbloco.c -o finalbloco
finalbloco.c: In function ‘main’:
finalbloco.c:9:5: warning: label at end of compound statement [-Wfree-labels]
9 | proximo:
| ^~~~~~~
Com -std=c17, o gcc só avisa se você pedir o rigor do padrão (-pedantic); o Clang avisa sempre: label at end of compound statement is a C23 extension. Este goto proximo, aliás, é só um continue disfarçado (aula break e continue): prefira o continue.
5. O futuro: break com nome C2y
O uso mais comum do goto fora da limpeza é sair de vários laços. A próxima versão do C, o C2y, traz um jeito sem goto: dar nome ao laço e escrever break nome;. O gcc 15 e o Clang 22 já aceitam com -std=c2y:
#include <stdio.h>
int main(void) {
busca: for (int a = 1; a <= 20; a++) {
for (int b = a; b <= 20; b++) {
if (a * b == 36 && a + b == 13) {
printf("Achei: %d e %d\n", a, b);
break busca;
}
}
}
printf("Busca encerrada.\n");
return 0;
}
$ gcc -std=c2y -Wall -Wextra nomeado.c -o nomeado && ./nomeado
Achei: 4 e 9
Busca encerrada.
$ gcc -std=c23 -Wall -Wextra -pedantic nomeado.c -o nomeado
nomeado.c: In function ‘main’:
nomeado.c:8:23: warning: ISO C does not support ‘break’ statement with an identifier operand before C2Y [-Wpedantic]
8 | break busca;
| ^~~~~
- busca: for (…)
- O nome vai antes do laço, com a mesma cara de um rótulo.
- break busca;
- Sai do laço chamado
busca, com o de dentro junto. Existe tambémcontinue busca;, que pula para a próxima volta do laço de fora.
Com -std=c23, o gcc 15 aceita sem dizer nada (só avisa com -pedantic, como acima), mas o Clang recusa: error: named 'break' is only supported in C2y. Como o C2y ainda não saiu, use isto só para conhecer; em código de hoje, fique com a bandeira, a função com return ou o goto.
Missão: esta caixa compila com -std=c2y. O programa conta quantas voltas o laço de dentro deu. Rode: com o break; comum, só o laço de dentro para, o de fora continua até a = 20, e são 199 voltas (o Clang ainda avisa unused label 'busca'). Troque por break busca; para encerrar a busca no par certo: devem sobrar 63 voltas.
#include <stdio.h>
int main(void) {
int voltas = 0;
busca: for (int a = 1; a <= 20; a++) {
for (int b = a; b <= 20; b++) {
voltas++;
if (a * b == 36 && a + b == 13) {
printf("Achei: %d e %d\n", a, b);
break;
}
}
}
printf("Busca encerrada depois de %d voltas.\n", voltas);
return 0;
}
#include <stdio.h>
int main(void) {
int voltas = 0;
busca: for (int a = 1; a <= 20; a++) {
for (int b = a; b <= 20; b++) {
voltas++;
if (a * b == 36 && a + b == 13) {
printf("Achei: %d e %d\n", a, b);
break busca;
}
}
}
printf("Busca encerrada depois de %d voltas.\n", voltas);
return 0;
}
Qual usar
| Você quer… | Use |
|---|---|
| Repetir enquanto não der certo | while ou do while, nunca goto para trás |
| Pular o resto de uma volta do laço | continue |
| Sair de um laço | break |
| Sair de vários laços de uma vez | Pôr os laços numa função e usar return; ou uma bandeira; ou goto para a frente (no futuro, break nome;) |
| Sair de uma função com erro, devolvendo o que ela reservou | goto para o bloco de limpeza, com os rótulos em escada (ou if aninhados, se forem poucos recursos) |
| Voltar de várias funções de uma vez | Códigos de retorno, ou setjmp e longjmp |
Regra de bolso: todo goto deve pular para a frente e para um rótulo perto do fim da função. Se você precisa desenhar setas para explicar um goto, troque-o por um laço ou por uma função.
Quando dá errado
error: label ‘fin’ used but not defined
O que significa: o goto aponta para um rótulo que não existe nesta função: o nome está escrito diferente, ou o rótulo está em outra função. No editor: use of undeclared label 'fin'.
Como consertar: confira o nome (maiúsculas contam). Se o rótulo está em outra função, use return para sair desta.
warning: label ‘fim’ defined but not used [-Wunused-label]
O que significa: nenhum goto aponta para este rótulo. Ou sobrou de uma versão antiga, ou o goto que devia usá-lo tem o nome errado (veja o erro de cima). No editor: unused label 'fim'.
Como consertar: apague o rótulo, ou conserte o nome no goto.
error: duplicate label ‘fim’
O que significa: dois rótulos com o mesmo nome na mesma função. No editor: redefinition of label 'fim'.
Como consertar: dê nomes que digam o que o rótulo faz: fecha_arquivo, libera_precos.
error: jump into scope of identifier with variably modified type
O que significa: o goto pula para dentro do bloco de um VLA, depois da declaração dele. No editor: cannot jump from this goto statement to its label.
Como consertar: ponha o rótulo fora do bloco do VLA (ou declare o array antes do goto).
‘x’ is used uninitialized
O que significa: um goto pulou a linha que dava o valor inicial. A variável existe, mas o valor nunca foi guardado.
Como consertar: declare e inicialize as variáveis antes do primeiro goto. Para o gcc avisar sempre, compile com -Wjump-misses-init.
valgrind numa função com vários return
O que significa: um dos caminhos de erro saiu sem free ou sem fclose.
Como consertar: troque os return do meio por goto para os rótulos de limpeza, em escada, e deixe um return só, no fim.
Referência
| Escrito | O que é |
|---|---|
nome: | Rótulo: marca uma linha. Vale na função inteira |
goto nome; | Pula para o rótulo, na mesma função |
libera: free(p); fecha: fclose(f); fim: return r; | Bloco de limpeza, na ordem inversa da reserva |
nome: } | Rótulo no fim do bloco C23 (antes: nome: ; }) |
nome: for (…) { … break nome; } | break/continue com nome C2y |
-Wjump-misses-init | Opção do gcc: avisa todo pulo por cima de uma inicialização |
Exercícios
1. O rótulo
Complete a linha do rótulo para onde vai o goto fecha_arquivo;:
fclose(f);
2. Sem pulo para trás
Um goto para trás que repete um pedido até a resposta ser válida deve virar qual laço? Escreva as duas palavras:
3. Uma função abre um arquivo e depois reserva memória. Em que ordem ficam os rótulos de limpeza no fim?
4. O goto pode pular para um rótulo de outra função?
5. goto mostrar; int estoque = 12; mostrar: printf("%d", estoque); faz o quê?
Missão final: tape os vazamentos
Missão: a função maior_preco tem dois return -1 que esquecem de liberar a memória e de fechar o arquivo (as funções liberar e fechar avisam na tela quando são chamadas). Reescreva a função com um bloco de limpeza: um int resultado = -1; no começo, goto nos erros, rótulos em escada no fim e um return resultado; só. Todo arquivo aberto tem de ser fechado, e toda memória, liberada:
precos.txt: 4 preços, o maior é R$ 25 (liberei a memória) (fechei precos.txt) vazio.txt: nenhum preço (liberei a memória) (fechei vazio.txt) erro.txt: preço suspeito (R$ 3000) (liberei a memória) (fechei erro.txt) sumiu.txt: não abriu
#include <stdio.h>
#include <stdlib.h>
void fechar(FILE *f, const char *nome) {
fclose(f);
printf(" (fechei %s)\n", nome);
}
void liberar(int *p) {
free(p);
printf(" (liberei a memória)\n");
}
int maior_preco(const char *nome) {
FILE *f = fopen(nome, "r");
if (f == nullptr) {
printf("%s: não abriu\n", nome);
return -1;
}
int *precos = malloc(100 * sizeof *precos);
if (precos == nullptr) {
printf("Sem memória\n");
fechar(f, nome);
return -1;
}
int n = 0;
while (n < 100 && fscanf(f, "%d", &precos[n]) == 1) {
n++;
}
if (n == 0) {
printf("%s: nenhum preço\n", nome);
return -1;
}
int maior = precos[0];
for (int i = 1; i < n; i++) {
if (precos[i] > maior) {
maior = precos[i];
}
}
if (maior > 1000) {
printf("%s: preço suspeito (R$ %d)\n", nome, maior);
return -1;
}
printf("%s: %d preços, o maior é R$ %d\n", nome, n, maior);
liberar(precos);
fechar(f, nome);
return maior;
}
int main(void) {
maior_preco("precos.txt");
maior_preco("vazio.txt");
maior_preco("erro.txt");
maior_preco("sumiu.txt");
return 0;
}
12 3 25 8
12 3000 8
#include <stdio.h>
#include <stdlib.h>
void fechar(FILE *f, const char *nome) {
fclose(f);
printf(" (fechei %s)\n", nome);
}
void liberar(int *p) {
free(p);
printf(" (liberei a memória)\n");
}
int maior_preco(const char *nome) {
int resultado = -1;
int *precos = nullptr;
FILE *f = fopen(nome, "r");
if (f == nullptr) {
printf("%s: não abriu\n", nome);
goto fim;
}
precos = malloc(100 * sizeof *precos);
if (precos == nullptr) {
printf("Sem memória\n");
goto fecha_arquivo;
}
int n = 0;
while (n < 100 && fscanf(f, "%d", &precos[n]) == 1) {
n++;
}
if (n == 0) {
printf("%s: nenhum preço\n", nome);
goto libera_precos;
}
int maior = precos[0];
for (int i = 1; i < n; i++) {
if (precos[i] > maior) {
maior = precos[i];
}
}
if (maior > 1000) {
printf("%s: preço suspeito (R$ %d)\n", nome, maior);
goto libera_precos;
}
printf("%s: %d preços, o maior é R$ %d\n", nome, n, maior);
resultado = maior;
libera_precos:
liberar(precos);
fecha_arquivo:
fechar(f, nome);
fim:
return resultado;
}
int main(void) {
maior_preco("precos.txt");
maior_preco("vazio.txt");
maior_preco("erro.txt");
maior_preco("sumiu.txt");
return 0;
}
Resumo
goto nome;pula para o rótulonome:, que vale na função inteira e só nela.- Pular para trás cria "código espaguete": use
while,do while,breakecontinue. - O uso aceito: sair para a frente. Para sair de vários laços (ou, melhor, pôr os laços numa função e usar
return) e, principalmente, para o bloco de limpeza: rótulos em escada no fim da função, na ordem inversa da reserva, e umreturnsó. Ovalgrindconfere que nenhum caminho vaza. - Não dá para pular para dentro do escopo de um VLA. Pular por cima de uma inicialização é permitido, mas deixa a variável com lixo: declare tudo antes do primeiro
goto(e-Wjump-misses-initavisa). - No C23, um rótulo pode ficar no fim do bloco. No C2y,
break nome;vai sair de vários laços semgoto. - Próxima aula: setjmp e longjmp, o "goto" que atravessa funções, e por que ele é ainda mais perigoso.