C setjmp e longjmp
Um erro acontece lá no fundo do programa: o main chamou media, que chamou ler_nota, que achou a nota "oito" escrita por extenso. Para avisar o main, cada função teria de devolver um código de erro, e cada uma que chamou teria de conferir esse código e passá-lo adiante. Em C existe um atalho: setjmp marca um ponto do programa e longjmp volta direto para ele, de qualquer profundidade, pulando todas as funções do meio. É poderoso e é perigoso. Nesta aula você vai usar os dois, ver o que eles fazem com as variáveis e com a memória, e aprender quando não usar.
- O que
setjmp("set jump", marcar o pulo) elongjmp("long jump", pulo longo) fazem, e o tipojmp_bufque guarda o ponto marcado. - Por que o
setjmp"devolve duas vezes": 0 ao marcar e outro número ao voltar. - Como tratar erros de várias funções num lugar só, com um
switch. - Por que variáveis locais mudadas depois do
setjmpprecisam devolatile(e o que o-O2faz sem ele). - Os perigos: memória que nunca é liberada e pular para uma função que já terminou.
- Quando usar
setjmpe quando um simples código de retorno é melhor.
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/setjmp, com gcc -std=c23 -Wall -Wextra arquivo.c -o programa.
A ideia: o ponto de salvamento do jogo
A Ana está jogando um videogame. Antes de entrar na caverna do chefe, ela passa por um ponto de salvamento: o jogo anota onde ela está. Lá dentro, ela desce três andares, abre portas, pega moedas. Aí o personagem cai num buraco. O jogo não a leva de volta andar por andar: ela reaparece direto no ponto de salvamento.
setjmpé passar pelo ponto de salvamento: o programa anota onde está.longjmpé cair no buraco: o programa volta direto para o ponto anotado, saindo de todas as funções do meio de uma vez.- E o jogo avisa por que você voltou ("Você caiu!"). O
longjmptambém leva um número que diz o motivo.
Uma diferença importante: o jogo de verdade desfaz tudo o que você fez depois de salvar. O longjmp não desfaz nada: a memória reservada continua reservada, os arquivos abertos continuam abertos. Isso vai ser o perigo da seção 5.
1. O primeiro pulo
#include <stdio.h>
#include <setjmp.h>
jmp_buf ponto;
void tarefa(void) {
printf("Dentro da tarefa: vou pular de volta.\n");
longjmp(ponto, 1);
printf("Esta linha nunca aparece.\n");
}
int main(void) {
if (setjmp(ponto) == 0) {
printf("Marquei o ponto. Chamando a tarefa...\n");
tarefa();
printf("Nem esta.\n");
} else {
printf("Voltei para o ponto marcado!\n");
}
printf("Fim.\n");
return 0;
}
- #include <setjmp.h>
- O cabeçalho que traz
jmp_buf,setjmpelongjmp. Faz parte do C padrão desde o C89: funciona em qualquer sistema. - jmp_buf ponto;
- "Jump buffer", a caixa que guarda o ponto marcado: onde o programa estava, a posição da pilha e os registradores do processador (aula Pilha e Heap). Fica fora das funções (global) para que
mainetarefaenxerguem a mesma caixa. - setjmp(ponto)
- Anota o ponto atual dentro de
pontoe devolve 0. Com 0, oifsegue pelo primeiro bloco. - tarefa();
- Chamada normal de função.
- longjmp(ponto, 1)
- Volta para o
setjmpque preencheuponto. Atarefaacaba ali, semreturn. O programa continua como se osetjmpestivesse devolvendo de novo, agora com o valor 1. - else
- Como o
setjmp"devolveu" 1, oifé testado de novo e cai noelse.
$ gcc -std=c23 -Wall -Wextra primeiro.c -o primeiro $ ./primeiro Marquei o ponto. Chamando a tarefa... Dentro da tarefa: vou pular de volta. Voltei para o ponto marcado! Fim.
Siga a ordem das linhas: o if rodou duas vezes, uma com 0 e outra com 1. As duas frases "Esta linha nunca aparece" e "Nem esta" ficaram para trás: o longjmp saiu da tarefa e do primeiro bloco do if de uma vez.
| Momento | O que setjmp(ponto) devolve | Por onde o if segue |
|---|---|---|
| Primeira vez, ao marcar o ponto | 0 | Bloco do == 0: chama a tarefa |
Depois do longjmp(ponto, 1) | 1 (o número do longjmp) | else: trata a volta |
Experimente: troque o 1 do longjmp por 7 e rode: muda alguma coisa? (O else pega qualquer número diferente de 0.) Depois apague a linha do longjmp: a tarefa termina com o fim normal da função. Quais frases aparecem agora, e qual some?
#include <stdio.h>
#include <setjmp.h>
jmp_buf ponto;
void tarefa(void) {
printf("Dentro da tarefa: vou pular de volta.\n");
longjmp(ponto, 1);
printf("Esta linha nunca aparece.\n");
}
int main(void) {
if (setjmp(ponto) == 0) {
printf("Marquei o ponto. Chamando a tarefa...\n");
tarefa();
printf("Nem esta.\n");
} else {
printf("Voltei para o ponto marcado!\n");
}
printf("Fim.\n");
return 0;
}
2. O número do motivo: switch (setjmp(...))
O segundo argumento do longjmp é o "Você caiu!" do jogo: diz por que o programa voltou. Com vários motivos possíveis, o jeito natural é um switch (aula switch) com um enum de nomes para os números (aula Enums). Este programa calcula a média de três alunos. As notas chegam como texto, e o erro é descoberto lá no fundo, em ler_nota:
#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>
enum { NAO_E_NUMERO = 1, FORA_DA_FAIXA = 2 };
jmp_buf erro;
double ler_nota(const char *texto) {
char *fim;
double nota = strtod(texto, &fim);
if (fim == texto || *fim != '\0') {
longjmp(erro, NAO_E_NUMERO);
}
if (nota < 0 || nota > 10) {
longjmp(erro, FORA_DA_FAIXA);
}
return nota;
}
double media(const char *notas[], int quantas) {
double soma = 0;
for (int i = 0; i < quantas; i++) {
soma += ler_nota(notas[i]);
}
return soma / quantas;
}
int main(void) {
const char *nomes[] = {"Ana", "Bruno", "Carla"};
const char *notas[3][3] = {
{"7", "8", "9"},
{"6", "oito", "8"},
{"10", "11", "5"},
};
for (int i = 0; i < 3; i++) {
switch (setjmp(erro)) {
case 0:
printf("%s: média %.1f\n", nomes[i], media(notas[i], 3));
break;
case NAO_E_NUMERO:
printf("%s: uma das notas não é um número\n", nomes[i]);
break;
case FORA_DA_FAIXA:
printf("%s: uma das notas está fora de 0 a 10\n", nomes[i]);
break;
}
}
return 0;
}
| Linha | O que faz |
|---|---|
enum { NAO_E_NUMERO = 1, FORA_DA_FAIXA = 2 }; | Dá nome aos motivos. Começa em 1 porque o 0 é reservado para "acabei de marcar o ponto". |
strtod(texto, &fim) | Converte o texto em double e aponta fim para o que sobrou (aula Texto ↔ Número). |
longjmp(erro, NAO_E_NUMERO) | Sai de ler_nota e de media de uma vez e volta ao switch do main, com o valor 1. |
switch (setjmp(erro)) | Marca o ponto a cada aluno. Na primeira passada vale 0 (case 0: calcula a média); depois de um pulo, vale o motivo. |
case 0: … media(…) | O caminho feliz. Se nenhuma nota tiver problema, media devolve normalmente e o printf mostra o resultado. |
$ gcc -std=c23 -Wall -Wextra boletim.c -o boletim $ ./boletim Ana: média 8.0 Bruno: uma das notas não é um número Carla: uma das notas está fora de 0 a 10
Repare que media não tem nenhuma linha sobre erros: ela soma e divide, e só. Toda a decisão ficou no main. O setjmp é chamado de novo a cada volta do for, então cada aluno tem o seu ponto de salvamento: o erro do Bruno não atrapalha a Carla.
O longjmp com 0 vira 1
Se o longjmp devolvesse 0, o programa não teria como saber se acabou de marcar o ponto ou se voltou. Por isso a regra: longjmp(ponto, 0) faz o setjmp devolver 1.
#include <stdio.h>
#include <setjmp.h>
jmp_buf ponto;
int main(void) {
switch (setjmp(ponto)) {
case 0:
printf("Primeira vez: setjmp devolveu 0\n");
longjmp(ponto, 0);
case 1:
printf("Voltei: setjmp devolveu 1\n");
break;
}
return 0;
}
$ gcc -std=c23 -Wall -Wextra zero.c -o zero $ ./zero Primeira vez: setjmp devolveu 0 Voltei: setjmp devolveu 1
Pedimos 0 e veio 1. O case 0 não precisa de break depois do longjmp, e o -Wextra nem reclama de "cair no próximo case": o longjmp é uma função que nunca volta (no cabeçalho ela é marcada com noreturn; aula Atributos), e o compilador sabe disso.
Experimente: o boletim roda igual aqui no editor. Acrescente um terceiro motivo, NOTA_VAZIA = 3: em ler_nota, se texto[0] == '\0', pule com esse motivo (antes do strtod), e trate o novo case no main. Teste trocando o "9" da Ana por "". Sem o seu teste, uma nota vazia seria "não é um número": qual mensagem é mais útil para quem digitou?
#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>
enum { NAO_E_NUMERO = 1, FORA_DA_FAIXA = 2 };
jmp_buf erro;
double ler_nota(const char *texto) {
char *fim;
double nota = strtod(texto, &fim);
if (fim == texto || *fim != '\0') {
longjmp(erro, NAO_E_NUMERO);
}
if (nota < 0 || nota > 10) {
longjmp(erro, FORA_DA_FAIXA);
}
return nota;
}
double media(const char *notas[], int quantas) {
double soma = 0;
for (int i = 0; i < quantas; i++) {
soma += ler_nota(notas[i]);
}
return soma / quantas;
}
int main(void) {
const char *nomes[] = {"Ana", "Bruno", "Carla"};
const char *notas[3][3] = {
{"7", "8", "9"},
{"6", "oito", "8"},
{"10", "11", "5"},
};
for (int i = 0; i < 3; i++) {
switch (setjmp(erro)) {
case 0:
printf("%s: média %.1f\n", nomes[i], media(notas[i], 3));
break;
case NAO_E_NUMERO:
printf("%s: uma das notas não é um número\n", nomes[i]);
break;
case FORA_DA_FAIXA:
printf("%s: uma das notas está fora de 0 a 10\n", nomes[i]);
break;
}
}
return 0;
}
3. Onde o setjmp pode ficar
O setjmp parece uma função comum, mas não é: ele "devolve duas vezes", e o compilador precisa de cuidado especial com o código em volta. Por isso o padrão C só permite usá-lo em quatro formas:
| Forma | Exemplo |
|---|---|
Sozinho, como condição de if, switch, while ou for | switch (setjmp(erro)) |
| Comparado com um número fixo, como condição | if (setjmp(ponto) == 0), if (setjmp(ponto) != 0) |
Com !, como condição | if (!setjmp(ponto)) |
| Como uma instrução sozinha, ignorando o valor | setjmp(ponto); |
Guardar o valor numa variável, como int r = setjmp(ponto);, ou usá-lo numa conta, como x = setjmp(ponto) + 1;, não está na lista. O gcc compila sem avisar, e muitas vezes funciona, mas é comportamento indefinido. Precisa do número? Use o switch, que entrega o valor a cada case.
4. Variáveis locais e o -O2: volatile
O jmp_buf guarda os registradores do processador (as gavetinhas rápidas onde o compilador gosta de deixar as variáveis). Quando o longjmp volta, ele põe nos registradores os valores da hora do setjmp. Se uma variável local mudou depois do setjmp e estava num registrador, a mudança se perde. Veja:
#include <stdio.h>
#include <setjmp.h>
jmp_buf ponto;
void desistir(void) {
longjmp(ponto, 1);
}
int main(void) {
int tentativas = 0;
if (setjmp(ponto) != 0) {
printf("Desisti depois de %d tentativas.\n", tentativas);
return 0;
}
for (;;) {
tentativas++;
if (tentativas == 3) {
desistir();
}
}
}
- int tentativas = 0;
- Variável local do
main, criada antes dosetjmp. - if (setjmp(ponto) != 0)
- Na primeira passada, 0: pula o bloco. Depois do
longjmp, 1: mostra quantas tentativas houve. - for (;;)
- Um laço sem condição (repete para sempre). Quem o encerra é o
longjmpdentro dedesistir. - tentativas++
- A variável muda depois do
setjmp. É aqui que mora o problema.
$ gcc -std=c23 -Wall -Wextra desistir.c -o desistir $ ./desistir Desisti depois de 3 tentativas. $ gcc -std=c23 -Wall -Wextra -O2 desistir.c -o desistir-O2 $ ./desistir-O2 Desisti depois de 0 tentativas. « o mesmo código, outra resposta
Sem otimização, o gcc guarda tentativas na memória a cada mudança, e o printf lê o 3. Com -O2, ele mantém a variável num registrador, e o longjmp devolveu ao registrador o valor da hora do setjmp: 0. Nenhum aviso, nenhum erro. O padrão diz que, nesse caso, o valor da variável depois do pulo é indeterminado.
O conserto é a palavra volatile (aula volatile e restrict), que obriga o compilador a ler e escrever a variável na memória toda vez:
volatile int tentativas = 0;
$ gcc -std=c23 -Wall -Wextra -O2 desistir2.c -o desistir2-O2 $ ./desistir2-O2 Desisti depois de 3 tentativas.
setjmp, que muda depois do setjmp e é lida depois do longjmp, tem de ser volatile. Variáveis globais (como o jmp_buf) não têm o problema: elas moram na memória, não nos registradores.
Missão: esta caixa compila com -O2, como o segundo terminal. Rode e veja o 0 (o Clang do editor faz a mesma otimização que o gcc). Depois conserte com volatile para o programa mostrar Desisti depois de 3 tentativas.
#include <stdio.h>
#include <setjmp.h>
jmp_buf ponto;
void desistir(void) {
longjmp(ponto, 1);
}
int main(void) {
int tentativas = 0;
if (setjmp(ponto) != 0) {
printf("Desisti depois de %d tentativas.\n", tentativas);
return 0;
}
for (;;) {
tentativas++;
if (tentativas == 3) {
desistir();
}
}
}
#include <stdio.h>
#include <setjmp.h>
jmp_buf ponto;
void desistir(void) {
longjmp(ponto, 1);
}
int main(void) {
volatile int tentativas = 0;
if (setjmp(ponto) != 0) {
printf("Desisti depois de %d tentativas.\n", tentativas);
return 0;
}
for (;;) {
tentativas++;
if (tentativas == 3) {
desistir();
}
}
}
5. O longjmp não arruma a casa
Quando uma função termina com return, ela passa por todas as suas linhas finais: o free, o fclose. O longjmp não passa por nada: as funções do meio simplesmente somem da pilha. Tudo o que elas iam liberar fica para trás:
#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>
jmp_buf erro;
void cadastrar(const char *nome) {
char *ficha = malloc(64);
if (ficha == NULL) {
longjmp(erro, 2);
}
if (nome[0] == '\0') {
longjmp(erro, 1); // pula o free lá embaixo!
}
snprintf(ficha, 64, "Cliente: %s", nome);
printf("%s\n", ficha);
free(ficha);
}
int main(void) {
const char *clientes[] = {"Ana", "", "Bruno"};
for (int i = 0; i < 3; i++) {
if (setjmp(erro) == 0) {
cadastrar(clientes[i]);
} else {
printf("Cliente %d: nome vazio\n", i + 1);
}
}
return 0;
}
A saída parece perfeita. Mas o Valgrind (aula Valgrind), que confere cada malloc e cada free, conta outra história:
$ gcc -std=c23 -Wall -Wextra -g vazamento.c -o vazamento $ valgrind --leak-check=full ./vazamento ==419107== Memcheck, a memory error detector ==419107== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward et al. ==419107== Using Valgrind-3.26.0 and LibVEX; rerun with -h for copyright info ==419107== Command: ./vazamento ==419107== Cliente: Ana Cliente 2: nome vazio Cliente: Bruno ==419107== ==419107== HEAP SUMMARY: ==419107== in use at exit: 64 bytes in 1 blocks ==419107== total heap usage: 4 allocs, 3 frees, 4,288 bytes allocated ==419107== ==419107== 64 bytes in 1 blocks are definitely lost in loss record 1 of 1 ==419107== at 0x4850858: malloc (vg_replace_malloc.c:447) ==419107== by 0x4001242: cadastrar (vazamento.c:8) ==419107== by 0x4001330: main (vazamento.c:24) ==419107== ==419107== LEAK SUMMARY: ==419107== definitely lost: 64 bytes in 1 blocks ==419107== indirectly lost: 0 bytes in 0 blocks ==419107== possibly lost: 0 bytes in 0 blocks ==419107== still reachable: 0 bytes in 0 blocks ==419107== suppressed: 0 bytes in 0 blocks ==419107== ==419107== For lists of detected and suppressed errors, rerun with: -s ==419107== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
"64 bytes in 1 blocks are definitely lost": 64 bytes em 1 bloco com certeza perdidos, reservados na linha 8 (o malloc de cadastrar). É a ficha da cliente sem nome: o longjmp da linha 13 saiu da função antes do free. O total confirma: 4 reservas (as 3 fichas e o buffer do printf), só 3 liberações. Num programa que roda o dia inteiro, cada erro vaza mais um pouco (aula Vazamentos de Memória).
Dois consertos possíveis:
- Conferir antes de reservar. Se o teste do nome vier antes do
malloc, não há nada para liberar na hora do pulo. - Liberar antes de pular. Quando a reserva tem de vir primeiro:
free(ficha); longjmp(erro, 1);.
void cadastrar(const char *nome) {
if (nome[0] == '\0') {
longjmp(erro, 1); // confere ANTES de reservar
}
char *ficha = malloc(64);
if (ficha == NULL) {
longjmp(erro, 2);
}
snprintf(ficha, 64, "Cliente: %s", nome);
printf("%s\n", ficha);
free(ficha);
}
$ gcc -std=c23 -Wall -Wextra -g vazamento2.c -o vazamento2 $ valgrind ./vazamento2 ==418482== Memcheck, a memory error detector ==418482== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward et al. ==418482== Using Valgrind-3.26.0 and LibVEX; rerun with -h for copyright info ==418482== Command: ./vazamento2 ==418482== Cliente: Ana Cliente 2: nome vazio Cliente: Bruno ==418482== ==418482== HEAP SUMMARY: ==418482== in use at exit: 0 bytes in 0 blocks ==418482== total heap usage: 3 allocs, 3 frees, 4,224 bytes allocated ==418482== ==418482== All heap blocks were freed -- no leaks are possible ==418482== ==418482== For lists of detected and suppressed errors, rerun with: -s ==418482== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
"All heap blocks were freed": tudo o que foi reservado foi liberado. O mesmo vale para arquivos: um fopen sem o fclose por causa de um pulo deixa o arquivo aberto (e o que estava no buffer pode nunca ser gravado).
6. Nunca pule para uma função que já terminou
O jmp_buf guarda uma posição da pilha, o lugar onde moram as variáveis locais de cada função que está rodando. Quando a função que chamou o setjmp termina, o pedaço dela na pilha é reaproveitado pelas próximas chamadas. O ponto marcado vira um endereço de um lugar que não existe mais:
#include <stdio.h>
#include <setjmp.h>
jmp_buf ponto;
void marcar(void) {
if (setjmp(ponto) != 0) {
printf("Voltei para dentro de marcar.\n");
return;
}
printf("Marquei o ponto dentro de marcar.\n");
}
int main(void) {
marcar();
printf("marcar já terminou. Pulando...\n");
longjmp(ponto, 1);
return 0;
}
$ gcc -std=c23 -Wall -Wextra voltou.c -o voltou $ ./voltou Marquei o ponto dentro de marcar. marcar já terminou. Pulando... Voltei para dentro de marcar. Falha de segmentação (imagem do núcleo gravada) ./voltou $ echo $? 139 $ gcc -std=c23 -Wall -Wextra -O2 voltou.c -o voltou-O2 $ timeout 1 ./voltou-O2 | head -7 Marquei o ponto dentro de marcar. marcar já terminou. Pulando... Voltei para dentro de marcar. Marquei o ponto dentro de marcar. marcar já terminou. Pulando... Voltei para dentro de marcar. Marquei o ponto dentro de marcar. « e assim para sempre
O gcc compilou sem nenhum aviso. Sem otimização, o pulo "deu certo" por um instante (mostrou "Voltei…"), mas o return de marcar tentou voltar para um endereço que já não estava lá, e o programa morreu (139 = 128 + 11, o sinal SIGSEGV; aula Sinais). Com -O2, o return caiu de volta no começo do main, e o programa entrou num laço infinito (o timeout 1 o parou). No editor do guia, o mesmo código termina com Abortado (134). Três resultados diferentes: é comportamento indefinido.
longjmp só pode voltar para um setjmp de uma função que ainda está rodando (que chamou, direta ou indiretamente, quem faz o longjmp). No jogo: só dá para reaparecer num ponto de salvamento da fase em que você está.
setjmp ou código de retorno: qual usar?
| Forma | Quando usar |
|---|---|
Devolver um código de erro (return -1;, NULL, bool) e conferir em quem chamou | O padrão do C e da biblioteca (fopen, malloc, strtol). Claro, sem surpresas, fácil de liberar memória. Use quase sempre. |
goto para um bloco de limpeza no fim da função | Erros dentro de uma função que reservou várias coisas (aula goto). |
setjmp/longjmp | Erros descobertos muito fundo, em código com muitas camadas (um leitor de arquivos de configuração, um interpretador, uma calculadora de expressões), em que passar o código de volta por cada função poluiria tudo. Programas conhecidos, como o interpretador da linguagem Lua e a biblioteca de imagens libpng, usam assim. |
exit(1) | O erro é tão grave que não há o que fazer: termina o programa todo (aula exit e atexit). |
try/catch), que fazem o mesmo pulo para trás, mas arrumam a casa no caminho: liberam a memória e fecham arquivos sozinhas. O setjmp é a versão "faça você mesmo" do C. Para pular de volta de dentro de um tratador de sinal, o POSIX tem as versões sigsetjmp e siglongjmp (aula Sinais).
Quando dá errado
error: unknown type name ‘jmp_buf’
Um programa que usa setjmp, mas só tem #include <stdio.h> no topo:
$ gcc -std=c23 -Wall -Wextra semheader.c -o semheader
semheader.c:3:1: error: unknown type name ‘jmp_buf’
3 | jmp_buf ponto;
| ^~~~~~~
semheader.c: In function ‘main’:
semheader.c:6:9: error: implicit declaration of function ‘setjmp’ [-Wimplicit-function-declaration]
6 | if (setjmp(ponto) == 0) {
| ^~~~~~
semheader.c:7:9: error: implicit declaration of function ‘longjmp’ [-Wimplicit-function-declaration]
7 | longjmp(ponto, 1);
| ^~~~~~~
O que significa: "nome de tipo desconhecido: jmp_buf" e "declaração implícita da função setjmp": o compilador não conhece nenhum dos três nomes. No editor, o Clang diz unknown type name 'jmp_buf' e use of undeclared identifier 'setjmp'.
Como consertar: #include <setjmp.h> no topo.
O que significa: uma variável local mudou depois do setjmp e foi lida depois do longjmp, sem volatile (o desistir.c da seção 4). Costuma aparecer só com -O2, ou seja, na versão final do programa, e não enquanto você testa.
Como consertar: declare a variável como volatile, ou guarde o valor numa variável global.
definitely lost só quando há erro
O que significa: um longjmp saiu de uma função antes do free (ou do fclose), como no vazamento.c da seção 5. Quando tudo dá certo, não há vazamento; por isso é fácil não perceber.
Como consertar: faça as conferências antes de reservar, ou libere antes de chamar longjmp.
Falha de segmentação, laço infinito ou Abortado logo depois de um "Voltei"
O que significa: o longjmp foi para um setjmp de uma função que já tinha terminado (o voltou.c da seção 6). Nem o gcc nem o Clang avisam.
Como consertar: chame o setjmp numa função que continua rodando enquanto o pulo puder acontecer: normalmente no main, ou na função que chama todas as outras.
Referência rápida
| Escrita | O que faz |
|---|---|
#include <setjmp.h> | Traz jmp_buf, setjmp e longjmp (C padrão desde o C89) |
jmp_buf ponto; | A caixa que guarda o ponto marcado (normalmente global) |
setjmp(ponto) | Marca o ponto e devolve 0; depois de um longjmp, "devolve de novo" com o valor do pulo |
longjmp(ponto, n) | Volta para o setjmp, saindo de todas as funções do meio; o setjmp devolve n (ou 1, se n for 0). Nunca volta |
switch (setjmp(ponto)) | O jeito certo de usar o número do motivo |
volatile int x; | Para variáveis locais que mudam depois do setjmp e são lidas depois do pulo |
sigsetjmp, siglongjmp | Versões do POSIX para usar com sinais |
Exercícios
1. O cabeçalho
Complete o #include que traz setjmp e longjmp:
2. O tipo do ponto
Complete a declaração da caixa que guarda o ponto marcado:
3. Voltar com o motivo 2
Complete a linha que volta para o ponto erro com o motivo 2:
4. O programa faz longjmp(ponto, 0). O que o setjmp(ponto) devolve na volta?
5. Com -O2, uma variável local mostra depois do longjmp o valor que tinha na hora do setjmp. Qual é o conserto?
6. Uma função faz malloc, depois descobre um erro e chama longjmp. O que acontece com a memória?
7. Qual destes usos do setjmp o padrão C não permite?
Missão final: a calculadora que não cai
Missão: a calculadora da Ana faz três contas. A segunda divide por zero, e o programa morre com Exceção de ponto flutuante. O main já está pronto para tratar o erro. Falta só dividir pular de volta: se b for zero, chame longjmp(erro, 1) antes da divisão. A saída deve ser:
10 / 2 = 5 Conta 2: divisão por zero 9 / 3 = 3 Fim das contas.
#include <stdio.h>
#include <setjmp.h>
jmp_buf erro;
int dividir(int a, int b) {
// se b for zero, pule de volta para o setjmp com o valor 1
return a / b;
}
int main(void) {
int contas[3][2] = {{10, 2}, {7, 0}, {9, 3}};
for (int i = 0; i < 3; i++) {
int a = contas[i][0], b = contas[i][1];
if (setjmp(erro) == 0) {
printf("%d / %d = %d\n", a, b, dividir(a, b));
} else {
printf("Conta %d: divisão por zero\n", i + 1);
}
}
printf("Fim das contas.\n");
return 0;
}
#include <stdio.h>
#include <setjmp.h>
jmp_buf erro;
int dividir(int a, int b) {
// se b for zero, pule de volta para o setjmp com o valor 1
if (b == 0) {
longjmp(erro, 1);
}
return a / b;
}
int main(void) {
int contas[3][2] = {{10, 2}, {7, 0}, {9, 3}};
for (int i = 0; i < 3; i++) {
int a = contas[i][0], b = contas[i][1];
if (setjmp(erro) == 0) {
printf("%d / %d = %d\n", a, b, dividir(a, b));
} else {
printf("Conta %d: divisão por zero\n", i + 1);
}
}
printf("Fim das contas.\n");
return 0;
}
Repare que a, b e i não precisam de volatile: nenhuma delas muda entre o setjmp e o longjmp da mesma volta do laço. A cada volta, o setjmp marca um ponto novo.
Resumo
setjmp(ponto)marca um ponto e devolve 0;longjmp(ponto, n)volta para ele de qualquer profundidade, e osetjmp"devolve de novo", agora comn(ou 1, senfor 0).- Use o
setjmpsó como condição deif/switch(ou sozinho numa linha). Para vários motivos,switch (setjmp(ponto))com umenum. - Variáveis locais que mudam depois do
setjmpe são lidas depois do pulo precisam devolatile: com-O2, sem ele, o valor "volta no tempo". - O
longjmpnão roda o fim das funções que pula:freeefcloseficam para trás. Confira antes de reservar, ou libere antes de pular. - Nunca pule para um
setjmpde uma função que já terminou: é comportamento indefinido (segfault, laço infinito ouAbortado). - Na maioria dos programas, devolver um código de erro é mais simples e mais seguro. Guarde o
setjmppara erros muito fundos em código com muitas camadas.