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.

Você vai aprender
  • O que setjmp ("set jump", marcar o pulo) e longjmp ("long jump", pulo longo) fazem, e o tipo jmp_buf que 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 setjmp precisam de volatile (e o que o -O2 faz sem ele).
  • Os perigos: memória que nunca é liberada e pular para uma função que já terminou.
  • Quando usar setjmp e 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 longjmp també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, setjmp e longjmp. 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 main e tarefa enxerguem a mesma caixa.
setjmp(ponto)
Anota o ponto atual dentro de ponto e devolve 0. Com 0, o if segue pelo primeiro bloco.
tarefa();
Chamada normal de função.
longjmp(ponto, 1)
Volta para o setjmp que preencheu ponto. A tarefa acaba ali, sem return. O programa continua como se o setjmp estivesse devolvendo de novo, agora com o valor 1.
else
Como o setjmp "devolveu" 1, o if é testado de novo e cai no else.
$ 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.

MomentoO que setjmp(ponto) devolvePor onde o if segue
Primeira vez, ao marcar o ponto0Bloco 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;
}
LinhaO 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:

FormaExemplo
Sozinho, como condição de if, switch, while ou forswitch (setjmp(erro))
Comparado com um número fixo, como condiçãoif (setjmp(ponto) == 0), if (setjmp(ponto) != 0)
Com !, como condiçãoif (!setjmp(ponto))
Como uma instrução sozinha, ignorando o valorsetjmp(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 do setjmp.
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 longjmp dentro de desistir.
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.
A regra: uma variável local da função que chamou o 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:

  1. Conferir antes de reservar. Se o teste do nome vier antes do malloc, não há nada para liberar na hora do pulo.
  2. 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.

A regra: o 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?

FormaQuando usar
Devolver um código de erro (return -1;, NULL, bool) e conferir em quem chamouO 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çãoErros dentro de uma função que reservou várias coisas (aula goto).
setjmp/longjmpErros 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).
Outras linguagens: C++, Java e Python têm exceções (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 valor da variável "voltou no tempo" depois do pulo

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.

O Valgrind acusa 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

EscritaO 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, siglongjmpVersões do POSIX para usar com sinais

Exercícios

1. O cabeçalho

Complete o #include que traz setjmp e longjmp:

#include <>

2. O tipo do ponto

Complete a declaração da caixa que guarda o ponto marcado:

ponto;

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 o setjmp "devolve de novo", agora com n (ou 1, se n for 0).
  • Use o setjmp só como condição de if/switch (ou sozinho numa linha). Para vários motivos, switch (setjmp(ponto)) com um enum.
  • Variáveis locais que mudam depois do setjmp e são lidas depois do pulo precisam de volatile: com -O2, sem ele, o valor "volta no tempo".
  • O longjmp não roda o fim das funções que pula: free e fclose ficam para trás. Confira antes de reservar, ou libere antes de pular.
  • Nunca pule para um setjmp de uma função que já terminou: é comportamento indefinido (segfault, laço infinito ou Abortado).
  • Na maioria dos programas, devolver um código de erro é mais simples e mais seguro. Guarde o setjmp para erros muito fundos em código com muitas camadas.