O tratamento de exceções permite que a lógica do programa seja definida em um bloco, e caso alguma exceção seja disparada, esse fluxo é quebrado. Com a quebra desse fluxo, um novo fluxo ocorre.
Nesse novo fluxo, ocorre a pesquisa de um bloco catch, na ordem em que foram dispostos no código, que possa tratar a exceção. Caso seja um bloco catch compatível, ele é executado.
Diz-se então que esse bloco catch trata a exceção. O mecanismo da linguagem C que permite tal poder expressivo, suficiente para construirmos esse mecanismo é o goto, como mostra o exemplo abaixo:
// try {
if (b) {
c = a / b;
goto end;
} else {
// throw();
goto catch;
}
// catch
catch:
printf("Informe um número diferente de 0 para o denominador");
end:
É possível explorar outros recursos para alcançar um resultado similar, mas o goto foi escolhido para que a abordagem incremental prometida no começo desse texto possa ser utilizada. Outro motivo é que se fosse feita opção pelo não uso do goto, não seria possível quebrar o fluxo de execução de forma direta arbitrariamente, que é justamente o efeito almejado.
A limitação dessa abordagem é que com o goto não pode-se realizar saltos entre funções, impedindo que problemas sejam quebrados em funções (caso essas funções disparem exceções). Dessa forma, um dos objetivos de se introduzir o tratamento de exceções, que é melhorar a legibilidade do código, seria prejudicado.
int divide(int a, int b) throw()
{
if (b) {
return a / b;
} else {
throw();
}
}
Para resolver o problema, devemos utilizar um recurso que permita o salto entre funções. Felizmente, esse recurso está disponível na linguagem C, através do cabeçalho setjmp.h, que faz parte da biblioteca padrão, maximizando a portabilidade da implementação aqui sugerida.
Esse cabeçalho inclui o tipo jmp_buf (cujas variáveis armazenam informações sobre o ambiente de execução em um certo estado) e as funções setjmp (que guarda informações sobre o estado atual em uma variável jmp_buf) e longjmp (que recria o ambiente de execução descrito em uma variável jmp_buf).
A função setjmp recebe um argumento do tipo jmp_buf e retorna um inteiro, que é zero se o ocorrido foi uma simples chamada de função, ou não zero se o ocorrido foi uma chamada a longjmp, que recriou o ambiente de execução que havia sido registrado pela chamada a função setjmp.
Há algumas restrições do que pode-se fazer com as definições desse cabeçalho, mas ainda sim elas são adequadas ao nosso propósito.
int divide(int a, int b, jmp_buf catch)
{
if (b) {
return a / b;
} else {
// throw()
longjmp(catch, 1);
}
}
int main()
{
int a, b;
printf("Informe o divisor e o dividendo\n");
scanf("%d%d", &a, &b);
jmp_buf try;
//try
if (!setjmp(try)) {
printf("%i\n", divide(a, b, try));
// catch
} else {
printf("Informe um número diferente de 0 para o denominador\n");
}
}
No exemplo anterior, é declarada uma variável do tipo jmp_buf identificada por try dentro da função main, então a função setjmp é chamada, salvando o estado de execução atual para a variável try, e retorna 0, para indicar o fato.
Então a função divide é chamada, tendo como argumentos o divisor, o dividendo, e a variável do tipo jmp_buf. Em um caso normal, a função realiza o procedimento de dividir os dois operandos normalmente e imprime o resultado na tela.
Se uma divisão por zero for detectada, a função divide usa a variável jmp_buf como primeiro argumento da função longjmp e o inteiro 1 como segundo argumento para retornar ao estado em que o programa chama a função setjmp e interromper o fluxo do bloco try.
A "mágica" funciona graças a flexibilidade e o poder das funções setjmp e longjmp. O valor de retorno da função setjmp é zero quando o estado atual de execução está sendo salvo, ou o valor do segundo argumento da função longjmp quando o estado está sendo restaurado.
Uma limitação que existe nesse modelo (setjmp e longjmp), é que o comportamento do programa é indefinido se você usa algum recurso (goto) para pular para dentro do bloco try (e catch), pois possíveis exceções que sejam disparadas usarão a variável jmp_buf que contém informações sobre uma ambiente de execução inconsistente com o ambiente atual, ou nenhum ambiente.
Se for feito um pulo para um bloco catch a variável que contém o valor disparado pela exceção provavelmente conterá um valor inválido. Apesar desses problemas, é permitido pular para fora do bloco try (pular para fora do bloco catch não é recomendado), usando return, por exemplo.