Pruebe las declaraciones de captura en C

Estaba pensando hoy sobre los bloques try / catch existentes en otros idiomas. Busqué en Google por un tiempo, pero sin resultado. Por lo que sé, no hay tal cosa como try / catch en C. Sin embargo, ¿hay alguna forma de “simular”?
Claro, hay afirmación y otros trucos, pero nada como try / catch, que también atrape la excepción planteada. Gracias

C no admite excepciones, pero puede simularlas hasta cierto punto con llamadas setjmp y longjmp .

 static jmp_buf s_jumpBuffer; void Example() { if (setjmp(s_jumpBuffer)) { // The longjmp was executed and returned control here printf("Exception happened here\n"); } else { // Normal code execution starts here Test(); } } void Test() { // Rough equivalent of `throw` longjmp(s_jumpBuffer, 42); } 

Este sitio web tiene un buen tutorial sobre cómo simular excepciones con setjmp y longjmp

Utiliza goto en C para situaciones similares de manejo de errores.
Ese es el equivalente más cercano de excepciones que puede obtener en C.

En C99, puede usar setjmp / longjmp para flujo de control no local.

Dentro de un único ámbito, el patrón de encoding genérico estructurado para C en presencia de múltiples asignaciones de recursos y salidas múltiples usa goto , como en este ejemplo . Esto es similar a cómo C ++ implementa las llamadas de destrucción de objetos automáticos bajo el capó, y si se apega a esto diligentemente, debería permitirle un cierto grado de limpieza incluso en funciones complejas.

Ok, no pude resistirme a responder a esto. Permítanme primero decir que no creo que sea una buena idea simular esto en C, ya que es un concepto extraño para C.

Podemos usar el maltrato del preprocesador y las variables de la stack local para usar una versión limitada de C ++ try / throw / catch.

Versión 1 (scope local arroja)

 #include  #define try bool __HadError=false; #define catch(x) ExitJmp:if(__HadError) #define throw(x) __HadError=true;goto ExitJmp; 

La versión 1 es un lanzamiento local solamente (no puede abandonar el scope de la función). Depende de la capacidad de C99 para declarar variables en el código (debería funcionar en C89 si el bash es lo primero en la función).

Esta función solo hace una var local para saber si hubo un error y usa un goto para saltar al bloque catch.

Por ejemplo:

 #include  #include  #define try bool __HadError=false; #define catch(x) ExitJmp:if(__HadError) #define throw(x) __HadError=true;goto ExitJmp; int main(void) { try { printf("One\n"); throw(); printf("Two\n"); } catch(...) { printf("Error\n"); } return 0; } 

Esto funciona de la siguiente manera:

 int main(void) { bool HadError=false; { printf("One\n"); HadError=true; goto ExitJmp; printf("Two\n"); } ExitJmp: if(HadError) { printf("Error\n"); } return 0; } 

Versión 2 (scope saltando)

 #include  #include  jmp_buf *g__ActiveBuf; #define try jmp_buf __LocalJmpBuff;jmp_buf *__OldActiveBuf=g__ActiveBuf;bool __WasThrown=false;g__ActiveBuf=&__LocalJmpBuff;if(setjmp(__LocalJmpBuff)){__WasThrown=true;}else #define catch(x) g__ActiveBuf=__OldActiveBuf;if(__WasThrown) #define throw(x) longjmp(*g__ActiveBuf,1); 

La versión 2 es mucho más compleja, pero básicamente funciona de la misma manera. Utiliza un salto largo desde la función actual hasta el bloque try. El bloque try usa un if / else para omitir el bloque de código en el bloque de captura, que verifica la variable local para ver si debería atrapar.

El ejemplo se expandió de nuevo:

 jmp_buf *g_ActiveBuf; int main(void) { jmp_buf LocalJmpBuff; jmp_buf *OldActiveBuf=g_ActiveBuf; bool WasThrown=false; g_ActiveBuf=&LocalJmpBuff; if(setjmp(LocalJmpBuff)) { WasThrown=true; } else { printf("One\n"); longjmp(*g_ActiveBuf,1); printf("Two\n"); } g_ActiveBuf=OldActiveBuf; if(WasThrown) { printf("Error\n"); } return 0; } 

Utiliza un puntero global para que longjmp () sepa qué bash se ejecutó por última vez. Estamos usando abusando de la stack para que las funciones secundarias también puedan tener un bloque try / catch.

Usar este código tiene una cantidad de desventajas (pero es un ejercicio mental divertido):

  • No liberará memoria asignada ya que no se están llamando a deconstructors.
  • No puede tener más de 1 try / catch en un scope (sin anidar)
  • No puedes lanzar excepciones u otros datos como en C ++
  • No es seguro para nada
  • Estás configurando otros progtwigdores para el fracaso porque es probable que no noten el hack y traten de usarlos como C ++ try / catch blocks.

Esto se puede hacer con setjmp/longjmp en C. P99 tiene un conjunto de herramientas bastante cómodo para esto que también es consistente con el nuevo modelo de hilo de C11.

Mientras que algunas de las otras respuestas han cubierto casos simples usando setjmp y longjmp , en una aplicación real hay dos preocupaciones que realmente importan.

  1. Anidamiento de bloques try / catch. Usar una sola variable global para su jmp_buf hará que estos no funcionen.
  2. Enhebrado Una sola variable global para usted jmp_buf causará todo tipo de dolor en esta situación.

La solución a esto es mantener una stack local de jmp_buf de jmp_buf que se actualice a medida que jmp_buf . (Creo que esto es lo que usa lua internamente).

Entonces, en lugar de esto (de la increíble respuesta de JaredPar)

 static jmp_buf s_jumpBuffer; void Example() { if (setjmp(s_jumpBuffer)) { // The longjmp was executed and returned control here printf("Exception happened\n"); } else { // Normal code execution starts here Test(); } } void Test() { // Rough equivalent of `throw` longjump(s_jumpBuffer, 42); } 

Utilizarías algo como:

 #define MAX_EXCEPTION_DEPTH 10; struct exception_state { jmp_buf s_jumpBuffer[MAX_EXCEPTION_DEPTH]; int current_depth; }; int try_point(struct exception_state * state) { if(current_depth==MAX_EXCEPTION_DEPTH) { abort(); } int ok = setjmp(state->jumpBuffer[state->current_depth]); if(ok) { state->current_depth++; } else { //We've had an exception update the stack. state->current_depth--; } return ok; } void throw_exception(struct exception_state * state) { longjump(state->current_depth-1,1); } void catch_point(struct exception_state * state) { state->current_depth--; } void end_try_point(struct exception_state * state) { state->current_depth--; } __thread struct exception_state g_exception_state; void Example() { if (try_point(&g_exception_state)) { catch_point(&g_exception_state); printf("Exception happened\n"); } else { // Normal code execution starts here Test(); end_try_point(&g_exception_state); } } void Test() { // Rough equivalent of `throw` throw_exception(g_exception_state); } 

Una vez más, una versión más realista de esto incluiría alguna forma de almacenar información de error en el estado exception_state , un mejor manejo de MAX_EXCEPTION_DEPTH (quizás usando realloc para hacer crecer el búfer, o algo así).

DESCARGO DE RESPONSABILIDAD: El código anterior fue escrito sin ninguna prueba de ningún tipo. Es puramente para que tengas una idea de cómo estructurar las cosas. Diferentes sistemas y diferentes comstackdores necesitarán implementar el almacenamiento local de subprocesos de manera diferente. El código probablemente contenga tanto errores de comstackción como errores de lógica, así que mientras usted sea libre de usarlo como lo desee, pruébelo antes de usarlo;

Una búsqueda rápida en Google produce soluciones kludgey como esta que usan setjmp / longjmp como otros han mencionado. Nada tan sencillo y elegante como C ++ / Java’s try / catch. Soy bastante parcial a la excepción de Ada de manejarme.

Compruebe todo con if declaraciones 🙂

Advertencia: lo siguiente no es muy bueno, pero cumple su función.

 #include  #include  typedef struct { unsigned int id; char *name; char *msg; } error; #define _printerr(e, s, ...) fprintf(stderr, "\033[1m\033[37m" "%s:%d: " "\033[1m\033[31m" e ":" "\033[1m\033[37m" " '%s_error' " "\033[0m" s "\n", __FILE__, __LINE__, (*__err)->name, ##__VA_ARGS__) #define printerr(s, ...) _printerr("error", s, ##__VA_ARGS__) #define printuncaughterr() _printerr("uncaught error", "%s", (*__err)->msg) #define _errordef(n, _id) \ error* new_##n##_error_msg(char* msg) { \ error* self = malloc(sizeof(error)); \ self->id = _id; \ self->name = #n; \ self->msg = msg; \ return self; \ } \ error* new_##n##_error() { return new_##n##_error_msg(""); } #define errordef(n) _errordef(n, __COUNTER__ +1) #define try(try_block, err, err_name, catch_block) { \ error * err_name = NULL; \ error ** __err = & err_name; \ void __try_fn() try_block \ __try_fn(); \ void __catch_fn() { \ if (err_name == NULL) return; \ unsigned int __##err_name##_id = new_##err##_error()->id; \ if (__##err_name##_id != 0 && __##err_name##_id != err_name->id) \ printuncaughterr(); \ else if (__##err_name##_id != 0 || __##err_name##_id != err_name->id) \ catch_block \ } \ __catch_fn(); \ } #define throw(e) { *__err = e; return; } _errordef(any, 0) 

Uso:

 errordef(my_err1) errordef(my_err2) try ({ printf("Helloo\n"); throw(new_my_err1_error_msg("hiiiii!")); printf("This will not be printed!\n"); }, /*catch*/ any, e, { printf("My lovely error: %s %s\n", e->name, e->msg); }) printf("\n"); try ({ printf("Helloo\n"); throw(new_my_err2_error_msg("my msg!")); printf("This will not be printed!\n"); }, /*catch*/ my_err2, e, { printerr("%s", e->msg); }) printf("\n"); try ({ printf("Helloo\n"); throw(new_my_err1_error()); printf("This will not be printed!\n"); }, /*catch*/ my_err2, e, { printf("Catch %s if you can!\n", e->name); }) 

Salida:

 Helloo My lovely error: my_err1 hiiiii! Helloo /home/naheel/Desktop/aa.c:28: error: 'my_err2_error' my msg! Helloo /home/naheel/Desktop/aa.c:38: uncaught error: 'my_err1_error' 

Tenga en cuenta que esto está utilizando funciones anidadas y __COUNTER__ . Estarás seguro si estás usando gcc.

Esta es otra forma de manejar errores en C, que es más eficiente que usar setjmp / longjmp. Desafortunadamente, no funcionará con MSVC, pero si usar solo GCC / Clang es una opción, entonces podría considerarlo. Específicamente, utiliza la extensión “etiqueta como valor”, que le permite tomar la dirección de una etiqueta, almacenarla en un valor y saltar a ella sin condiciones. Lo presentaré usando un ejemplo:

 GameEngine *CreateGameEngine(GameEngineParams const *params) { /* Declare an error handler variable. This will hold the address to jump to if an error occurs to cleanup pending resources. Initialize it to the err label which simply returns an error value (NULL in this example). The && operator resolves to the address of the label err */ void *eh = &&err; /* Try the allocation */ GameEngine *engine = malloc(sizeof *engine); if (!engine) goto *eh; /* this is essentially your "throw" */ /* Now make sure that if we throw from this point on, the memory gets deallocated. As a convention you could name the label "undo_" followed by the operation to rollback. */ eh = &&undo_malloc; /* Now carry on with the initialization. */ engine->window = OpenWindow(...); if (!engine->window) goto *eh; /* The neat trick about using approach is that you don't need to remember what "undo" label to go to in code. Simply go to *eh. */ eh = &&undo_window_open; /* etc */ /* Everything went well, just return the device. */ return device; /* After the return, insert your cleanup code in reverse order. */ undo_window_open: CloseWindow(engine->window); undo_malloc: free(engine); err: return NULL; } 

Si lo desea, podría refactorizar código común en define, implementando efectivamente su propio sistema de manejo de errores.

 /* Put at the beginning of a function that may fail. */ #define declthrows void *_eh = &&err /* Cleans up resources and returns error result. */ #define throw goto *_eh /* Sets a new undo checkpoint. */ #define undo(label) _eh = &&undo_##label /* Throws if [condition] evaluates to false. */ #define check(condition) if (!(condition)) throw /* Throws if [condition] evaluates to false. Then sets a new undo checkpoint. */ #define checkpoint(label, condition) { check(condition); undo(label); } 

Entonces el ejemplo se convierte

 GameEngine *CreateGameEngine(GameEngineParams const *params) { declthrows; /* Try the allocation */ GameEngine *engine = malloc(sizeof *engine); checkpoint(malloc, engine); /* Now carry on with the initialization. */ engine->window = OpenWindow(...); checkpoint(window_open, engine->window); /* etc */ /* Everything went well, just return the device. */ return device; /* After the return, insert your cleanup code in reverse order. */ undo_window_open: CloseWindow(engine->window); undo_malloc: free(engine); err: return NULL; } 

Tal vez no sea un idioma importante (por desgracia), pero en APL, existe la operación ⎕EA (stand for Execute Alternate).

Uso: ‘Y’ ⎕EA ‘X’ donde X e Y son fragmentos de código suministrados como cadenas o nombres de funciones.

Si X se encuentra con un error, se ejecutará Y (generalmente manejo de errores).

Si está utilizando C con Win32, puede aprovechar su manejo estructurado de excepciones (SEH) para simular try / catch.

Si está utilizando C en plataformas que no son compatibles con setjmp() y longjmp() , eche un vistazo a este Manejo de excepciones de la biblioteca pjsip, pero proporciona su propia implementación

Redis usa goto para simular try / catch, en mi humilde opinión es muy limpio y elegante:

 /* Save the DB on disk. Return REDIS_ERR on error, REDIS_OK on success. */ int rdbSave(char *filename) { char tmpfile[256]; FILE *fp; rio rdb; int error = 0; snprintf(tmpfile,256,"temp-%d.rdb", (int) getpid()); fp = fopen(tmpfile,"w"); if (!fp) { redisLog(REDIS_WARNING, "Failed opening .rdb for saving: %s", strerror(errno)); return REDIS_ERR; } rioInitWithFile(&rdb,fp); if (rdbSaveRio(&rdb,&error) == REDIS_ERR) { errno = error; goto werr; } /* Make sure data will not remain on the OS's output buffers */ if (fflush(fp) == EOF) goto werr; if (fsync(fileno(fp)) == -1) goto werr; if (fclose(fp) == EOF) goto werr; /* Use RENAME to make sure the DB file is changed atomically only * if the generate DB file is ok. */ if (rename(tmpfile,filename) == -1) { redisLog(REDIS_WARNING,"Error moving temp DB file on the final destination: %s", strerror(errno)); unlink(tmpfile); return REDIS_ERR; } redisLog(REDIS_NOTICE,"DB saved on disk"); server.dirty = 0; server.lastsave = time(NULL); server.lastbgsave_status = REDIS_OK; return REDIS_OK; werr: fclose(fp); unlink(tmpfile); redisLog(REDIS_WARNING,"Write error saving DB on disk: %s", strerror(errno)); return REDIS_ERR; } 

En C, puede “simular” excepciones junto con la “recuperación de objetos” automática a través del uso manual de if + goto para el manejo de errores explícitos.

A menudo escribo un código C como el siguiente (reducido para resaltar el manejo de errores):

 #include  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; } 

Esto es completamente estándar ANSI C, separa el manejo de errores de su código de línea principal, permite el desenrollamiento de la stack (manual) de objetos inicializados de forma muy parecida a C ++, y es completamente obvio lo que está sucediendo aquí. Dado que está probando explícitamente el error en cada punto, facilita la inserción del registro específico o el manejo de errores en cada lugar donde se produce un error.

Si no te importa un poco de magia macro, puedes hacer que esto sea más conciso mientras haces otras cosas, como errores de registro con rastros de stack. Por ejemplo:

 #include  #include  #include  #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 C ++ excepciones + destructores. Por ejemplo, anidar múltiples stacks de manejo de errores dentro de una función de esta manera no es muy limpio. En su lugar, probablemente quiera dividirlos en subfunciones autónomas que manejen de manera similar los errores, inicialice + finalice explícitamente de esta manera.

Esto también solo funciona dentro de una única función y no seguirá saltando a la stack a menos que los llamadores de más alto nivel implementen una lógica de manejo de errores explícita similar, mientras que una excepción de C ++ seguirá saltando hasta que encuentre un controlador apropiado. Tampoco le permite arrojar un tipo arbitrario, sino solo un código de error.

La encoding sistemática de esta manera (es decir, con una sola entrada y un único punto de salida) también hace que sea muy fácil insertar una lógica pre y post (“finalmente”) que se ejecutará sin importar qué. Usted acaba de poner su lógica “finalmente” después de la etiqueta END.