Uma limitação das macros desenvolvidas e demonstradas na seção anterior é que não é possível aninhar o tratamento de exceções ou escrever funções que tratem exceções e a disparem para um escopo externo. O problema pode ser resolvido fazendo com que o usuário da biblioteca possa interferir no nome adotado para as variáveis que são essenciais para os procedimentos responsáveis pelo tratamento das exceções.
Há várias formas de permitir que o usuário consiga interferir no nome das variáveis usadas pelos procedimentos de tratamento de exceções descrito nesse texto, aqui adota-se uma técnica simples, onde todas as macros definidas devem receber um argumento adicional que servirá de sufixo para o nome das variáveis utilizadas:
#define TRY(x) \
{ \
jmp_buf _eb ## x; \
struct _gvalue _ev ## x = {NULL, NULL}; \
struct _gvalue *const _evp ## x = &_ev ## x; \
if (!setjmp(_eb ## x)) {
#define CATCH(_type, _value, x) \
} else if (_ev ## x.name && !strcmp(_ev ## x.name, #_type)) { \
_type _value = *((_type *)(_ev ## x.value));
#define ENDTRY(x) \
} \
if (_ev ## x.value) free(_ev ## x.value); \
}
#define THROW(_type, _value, x) \
{ \
if (_evp ## x) { \
_evp ## x->name = #_type; \
_evp ## x->value = malloc(sizeof(_type)); \
*((_type *)(_evp ## x->value)) = _value; \
} \
longjmp(_eb ## x, 1); \
}
#define TRY_ARGS(x) _eb ## x, _evp ## x
E para nomear os argumentos utilizados pelas funções que disparam as exceções, a macro THROW_NAMED_ARGS foi introduzida. Como consequências, a definição da macro THROW_ARGS pode ser simplificada. Dessa forma na assinatura da função, recomenda-se o uso de THROW_ARGS, e na sua implementação, o uso de THROW_NAMED_ARGS.
#define THROW_ARGS jmp_buf, struct _gvalue *
#define THROW_NAMED_ARGS(x) jmp_buf _eb ## x, struct _gvalue *_evp ## x
Com isso, podemos escrever códigos que aninham exceções como o do exemplo abaixo:
#include "cexceptions.h"
#include <stdio.h>
void a(THROW_ARGS); // throw const char *
void b(THROW_ARGS); // throw const char *
int main()
{
TRY()
a(TRY_ARGS());
CATCH(const char *, e,)
printf("Erro: %s\n", e);
ENDTRY()
return 0;
}
void a(THROW_NAMED_ARGS(_1))
{
TRY(_2)
b(TRY_ARGS(_2));
THROW(const char *, "disparando exceção da função \"a\" "
"de dentro de seu próprio bloco try", _2);
CATCH(const char *, i, _2)
THROW(const char *, "disparando exceção da função \"a\" "
"de dentro de seu próprio bloco catch", _1);
ENDTRY(_2)
}
void b(THROW_NAMED_ARGS())
{
THROW(const char *, "disparando exceção da função \"b\"",);
}
Esse código irá imprimir na tela:
"Erro: disparando exceção da função \"a\" de dentro de seu próprio bloco catch\n"
Se você leu o código com atenção, deve ter percebido que há um vazamento de memória. Na função a, uma chamada a função b é feita, então a função b aloca espaço para um ponteiro (que irá apontar para a string), então a função captura uma exceção e retorna do bloco CATCH (através da chamada a função longjmp) sem liberar o espaço alocado para o ponteiro (ele só seria liberado na linha de código onde ENDTRY se encontra).
Para resolver o problema, devemos introduzir uma nova macro, que libere os recursos alocados para o valor disparado:
#define EXCEPTION_FREE(x) \
{ \
if (_ev ## x.value) free(_ev ## x.value); \
_ev ## x.value = NULL; \
}
void a(THROW_NAMED_ARGS(_1))
{
TRY(_2)
b(TRY_ARGS(_2));
THROW(const char *, "disparando exceção da função \"a\" "
"de dentro de seu próprio bloco try", _2);
CATCH(const char *, i, _2)
EXCEPTION_FREE(_2)
THROW(const char *, "disparando exceção da função \"a\" "
"de dentro de seu próprio bloco catch", _1);
ENDTRY(_2)
}
Antes podíamos realizar saltos para fora de blocos TRY, agora podemos fazer o mesmo com blocos CATCH.