En C, puede "simular" excepciones junto con la "recuperación de objetos" automática mediante el uso manual de if + goto para el manejo explícito de errores.
A menudo escribo código C como el siguiente (resumido para resaltar el manejo de errores):
#include <assert.h>
typedef int errcode;
errcode init_or_fail( foo *f, goo *g, poo *p, loo *l )
{
errcode ret = 0;
if ( ( ret = foo_init( f ) ) )
goto FAIL;
if ( ( ret = goo_init( g ) ) )
goto FAIL_F;
if ( ( ret = poo_init( p ) ) )
goto FAIL_G;
if ( ( ret = loo_init( l ) ) )
goto FAIL_P;
assert( 0 == ret );
goto END;
/* error handling and return */
/* Note that we finalize in opposite order of initialization because we are unwinding a *STACK* of initialized objects */
FAIL_P:
poo_fini( p );
FAIL_G:
goo_fini( g );
FAIL_F:
foo_fini( f );
FAIL:
assert( 0 != ret );
END:
return ret;
}
Este es ANSI C completamente estándar, separa el manejo de errores de su código de línea principal, permite el desenrollado (manual) de la pila de objetos inicializados al igual que lo hace C ++, y es completamente obvio lo que está sucediendo aquí. Debido a que está probando explícitamente la falla en cada punto, hace que sea más fácil insertar registros específicos o manejo de errores en cada lugar donde puede ocurrir un error.
Si no le importa un poco de magia macro, puede hacerlo más conciso mientras hace otras cosas, como registrar errores con seguimientos de pila. Por ejemplo:
#include <assert.h>
#include <stdio.h>
#include <string.h>
#define TRY( X, LABEL ) do { if ( ( X ) ) { fprintf( stderr, "%s:%d: Statement '" #X "' failed! %d, %s\n", __FILE__, __LINE__, ret, strerror( ret ) ); goto LABEL; } while ( 0 )
typedef int errcode;
errcode init_or_fail( foo *f, goo *g, poo *p, loo *l )
{
errcode ret = 0;
TRY( ret = foo_init( f ), FAIL );
TRY( ret = goo_init( g ), FAIL_F );
TRY( ret = poo_init( p ), FAIL_G );
TRY( ret = loo_init( l ), FAIL_P );
assert( 0 == ret );
goto END;
/* error handling and return */
FAIL_P:
poo_fini( p );
FAIL_G:
goo_fini( g );
FAIL_F:
foo_fini( f );
FAIL:
assert( 0 != ret );
END:
return ret;
}
Por supuesto, esto no es tan elegante como las excepciones + destructores de C ++. Por ejemplo, anidar múltiples pilas de manejo de errores dentro de una función de esta manera no es muy limpio. En su lugar, probablemente desee dividirlos en subfunciones autónomas que manejen los errores de manera similar, inicializar + finalizar explícitamente de esta manera.
Esto también solo funciona dentro de una sola función y no seguirá saltando en la pila a menos que los llamadores de nivel superior implementen una lógica de manejo de errores explícita similar, mientras que una excepción de C ++ seguirá saltando en la pila hasta que encuentre un controlador apropiado. Tampoco le permite lanzar un tipo arbitrario, sino solo un código de error.
La codificación sistemática de esta manera (es decir, con una sola entrada y un solo punto de salida) también hace que sea muy fácil insertar lógica pre y post ("finalmente") que se ejecutará sin importar qué. Simplemente coloque su lógica "finalmente" después de la etiqueta FIN.