Uso práctico de setjmp y longjmp en C

¿Alguien me puede explicar dónde exactamente se pueden usar las funciones setjmp() y longjmp() prácticamente en la progtwigción integrada? Sé que estos son para el manejo de errores. Pero me gustaría saber algunos casos de uso.

Manejo de errores
Supongamos que hay un error en el fondo de una función anidada en muchas otras funciones y que el manejo de errores solo tiene sentido en la función de nivel superior.

Sería muy tedioso e incómodo si todas las funciones intermedias tuvieran que regresar normalmente y evaluar valores de retorno o una variable de error global para determinar que un mayor procesamiento no tiene sentido o incluso sería malo.

Esa es una situación en la que setjmp / longjmp tiene sentido. Esas situaciones son similares a las situaciones en las que la excepción en otros idiomas (C ++, Java) tiene sentido.

Coroutines
Además del manejo de errores, también puedo pensar en otra situación en la que necesitas setjmp / longjmp en C:

Es el caso cuando necesitas implementar coroutines .

Aquí hay un pequeño ejemplo de demostración. Espero que satisfaga la solicitud de Sivaprasad Palas para un código de ejemplo y responda a la pregunta de TheBlastOne de cómo setjmp / longjmp admite la implementación de corroutinas (por mucho que vea que no se basa en ningún comportamiento no estándar o nuevo).

EDITAR:
Podría ser que, en realidad, es un comportamiento indefinido hacer una secuencia larga en la stack de llamadas (vea el comentario de MikeMB; aunque todavía no he tenido la oportunidad de verificarlo).

 #include  #include  jmp_buf bufferA, bufferB; void routineB(); // forward declaration void routineA() { int r ; printf("(A1)\n"); r = setjmp(bufferA); if (r == 0) routineB(); printf("(A2) r=%d\n",r); r = setjmp(bufferA); if (r == 0) longjmp(bufferB, 20001); printf("(A3) r=%d\n",r); r = setjmp(bufferA); if (r == 0) longjmp(bufferB, 20002); printf("(A4) r=%d\n",r); } void routineB() { int r; printf("(B1)\n"); r = setjmp(bufferB); if (r == 0) longjmp(bufferA, 10001); printf("(B2) r=%d\n", r); r = setjmp(bufferB); if (r == 0) longjmp(bufferA, 10002); printf("(B3) r=%d\n", r); r = setjmp(bufferB); if (r == 0) longjmp(bufferA, 10003); } int main(int argc, char **argv) { routineA(); return 0; } 

La siguiente figura muestra el flujo de ejecución:
flujo de ejecución

Nota de advertencia
Cuando use setjmp / longjmp, tenga en cuenta que tienen un efecto en la validez de las variables locales que a menudo no se consideran.
Cf. Mi pregunta sobre este tema .

La teoría es que puede usarlos para el manejo de errores, de modo que puede saltar de una cadena de llamadas profundamente anidada sin tener que lidiar con errores de manejo en cada función de la cadena.

Como toda teoría inteligente, esto se derrumba cuando se enfrenta a la realidad. Sus funciones intermedias asignarán memoria, tomarán lockings, abrirán archivos y harán todo tipo de cosas diferentes que requieren limpieza. Por lo tanto, en la práctica, setjmp / longjmp suele ser una mala idea, excepto en circunstancias muy limitadas en las que tiene control total sobre su entorno (algunas plataformas integradas).

En mi experiencia, en la mayoría de los casos, cada vez que piensa que usar setjmp / longjmp funcionaría, su progtwig es lo suficientemente claro y simple como para que cada llamada de función intermedia en la cadena de llamadas pueda manejar el error, o es tan complicado que es imposible solucionarlo. exit cuando encuentre el error.

La combinación de setjmp y longjmp es “super strength goto “. Utilizar con cuidado EXTREMO. Sin embargo, como han explicado otros, un longjmp es muy útil para salir de una situación de error desagradable, cuando quiere que get me back to the beginning rápidamente, en lugar de tener que retocar un mensaje de error para 18 capas de funciones.

Sin embargo, al igual que goto , pero peor, tienes que ser REALMENTE cuidadoso en el uso de esto. Un longjmp solo te llevará al principio del código. No afectará a todos los demás estados que pueden haber cambiado entre setjmp y volver a donde comenzó setjmp . Por lo tanto, las asignaciones, los lockings, las estructuras de datos medio inicializadas, etc., aún están asignadas, bloqueadas y medio inicializadas cuando regresa a donde se llamó a setjmp . Esto significa que debes cuidar realmente los lugares donde haces esto, que REALMENTE está bien llamar a longjmp sin causar MÁS problemas. Por supuesto, si lo siguiente que hace es “reiniciar” [después de almacenar un mensaje sobre el error, tal vez], en un sistema integrado donde haya descubierto que el hardware está en mal estado, por ejemplo, entonces está bien.

También he visto setjmp / longjmp utilizado para proporcionar mecanismos de subprocesos muy básicos. Pero ese es un caso muy especial, y definitivamente no es como funcionan los hilos “estándar”.

Edición: Por supuesto, uno podría agregar código para “lidiar con la limpieza”, de la misma manera que C ++ almacena los puntos de excepción en el código comstackdo y luego sabe qué dio una excepción y qué necesita limpieza. Esto implicaría algún tipo de función de tabla de punteros y almacenamiento “si saltamos desde abajo aquí, llamemos a esta función con este argumento”. Algo como esto:

 struct { void (*destructor)(void *ptr); }; void LockForceUnlock(void *vlock) { LOCK* lock = vlock; } LOCK func_lock; void func() { ref = add_destructor(LockForceUnlock, mylock); Lock(func_lock) ... func2(); // May call longjmp. Unlock(func_lock); remove_destructor(ref); } 

Con este sistema, podría hacer “completar el manejo de excepciones como C ++”. Pero es bastante desordenado y confía en que el código esté bien escrito.

Dado que usted menciona incrustado, creo que vale la pena señalar un caso de no uso : cuando su norma de encoding lo prohíba. Por ejemplo, MISRA (MISRA-C: 2004: Rule 20.7) y JFS (AV Rule 20): “La macro setjmp y la función longjmp no se utilizarán”.

setjmp y longjmp pueden ser muy útiles en las pruebas unitarias.

Supongamos que queremos probar el siguiente módulo:

 #include  int my_div(int x, int y) { if (y==0) exit(2); return x/y; } 

Normalmente, si la función para probar llama a otra función, puede declarar una función de código auxiliar para que llame y que imite lo que hace la función real para probar ciertos flujos. En este caso, sin embargo, la función llama a exit que no vuelve. El talón necesita emular de alguna manera este comportamiento. setjmp y longjmp pueden hacer eso por ti.

Para probar esta función, podemos crear el siguiente progtwig de prueba:

 #include  #include  #include  #include  // redefine assert to set a boolean flag #ifdef assert #undef assert #endif #define assert(x) (rslt = rslt && (x)) // the function to test int my_div(int x, int y); // main result return code used by redefined assert static int rslt; // variables controling stub functions static int expected_code; static int should_exit; static jmp_buf jump_env; // test suite main variables static int done; static int num_tests; static int tests_passed; // utility function void TestStart(char *name) { num_tests++; rslt = 1; printf("-- Testing %s ... ",name); } // utility function void TestEnd() { if (rslt) tests_passed++; printf("%s\n", rslt ? "success" : "fail"); } // stub function void exit(int code) { if (!done) { assert(should_exit==1); assert(expected_code==code); longjmp(jump_env, 1); } else { _exit(code); } } // test case void test_normal() { int jmp_rval; int r; TestStart("test_normal"); should_exit = 0; if (!(jmp_rval=setjmp(jump_env))) { r = my_div(12,3); } assert(jmp_rval==0); assert(r==4); TestEnd(); } // test case void test_div0() { int jmp_rval; int r; TestStart("test_div0"); should_exit = 1; expected_code = 2; if (!(jmp_rval=setjmp(jump_env))) { r = my_div(2,0); } assert(jmp_rval==1); TestEnd(); } int main() { num_tests = 0; tests_passed = 0; done = 0; test_normal(); test_div0(); printf("Total tests passed: %d\n", tests_passed); done = 1; return !(tests_passed == num_tests); } 

En este ejemplo, usa setjmp antes de ingresar a la función para probar, luego en la exit aplastada llama a longjmp para regresar directamente a su caso de prueba.

También tenga en cuenta que la exit redefinida tiene una variable especial que verifica para ver si realmente desea salir del progtwig y llama a _exit para hacerlo. Si no hace esto, es posible que su progtwig de prueba no se cierre de forma limpia.

setjmp () y longjmp () son útiles para tratar los errores e interrupciones encontrados en una subrutina de bajo nivel de un progtwig.

Escribí un mecanismo de manejo de excepciones similar a Java en C usando setjmp() , longjmp() y funciones del sistema. Captura excepciones personalizadas pero también señales como SIGSEGV . Cuenta con un anidamiento infinito de bloques de manejo de excepciones, que funciona a través de llamadas a funciones, y es compatible con las dos implementaciones de subprocesos más comunes. Le permite definir una jerarquía de árbol de clases de excepción que cuentan con herencia de tiempo de enlace, y la instrucción catch recorre este árbol para ver si necesita capturar o transmitir.

Aquí hay una muestra de cómo se ve el código usando esto:

 try { *((int *)0) = 0; /* may not be portable */ } catch (SegmentationFault, e) { long f[] = { 'i', 'l', 'l', 'e', 'g', 'a', 'l' }; ((void(*)())f)(); /* may not be portable */ } finally { return(1 / strcmp("", "")); } 

Y aquí hay parte del archivo de inclusión que contiene mucha lógica:

 #ifndef _EXCEPT_H #define _EXCEPT_H #include  #include  #include  #include  #include "Lifo.h" #include "List.h" #define SETJMP(env) sigsetjmp(env, 1) #define LONGJMP(env, val) siglongjmp(env, val) #define JMP_BUF sigjmp_buf typedef void (* Handler)(int); typedef struct _Class *ClassRef; /* exception class reference */ struct _Class { int notRethrown; /* always 1 (used by throw()) */ ClassRef parent; /* parent class */ char * name; /* this class name string */ int signalNumber; /* optional signal number */ }; typedef struct _Class Class[1]; /* exception class */ typedef enum _Scope /* exception handling scope */ { OUTSIDE = -1, /* outside any 'try' */ INTERNAL, /* exception handling internal */ TRY, /* in 'try' (across routine calls) */ CATCH, /* in 'catch' (idem.) */ FINALLY /* in 'finally' (idem.) */ } Scope; typedef enum _State /* exception handling state */ { EMPTY, /* no exception occurred */ PENDING, /* exception occurred but not caught */ CAUGHT /* occurred exception caught */ } State; typedef struct _Except /* exception handle */ { int notRethrown; /* always 0 (used by throw()) */ State state; /* current state of this handle */ JMP_BUF throwBuf; /* start-'catching' destination */ JMP_BUF finalBuf; /* perform-'finally' destination */ ClassRef class; /* occurred exception class */ void * pData; /* exception associated (user) data */ char * file; /* exception file name */ int line; /* exception line number */ int ready; /* macro code control flow flag */ Scope scope; /* exception handling scope */ int first; /* flag if first try in function */ List * checkList; /* list used by 'catch' checking */ char* tryFile; /* source file name of 'try' */ int tryLine; /* source line number of 'try' */ ClassRef (*getClass)(void); /* method returning class reference */ char * (*getMessage)(void); /* method getting description */ void * (*getData)(void); /* method getting application data */ void (*printTryTrace)(FILE*);/* method printing nested trace */ } Except; typedef struct _Context /* exception context per thread */ { Except * pEx; /* current exception handle */ Lifo * exStack; /* exception handle stack */ char message[1024]; /* used by ExceptGetMessage() */ Handler sigAbrtHandler; /* default SIGABRT handler */ Handler sigFpeHandler; /* default SIGFPE handler */ Handler sigIllHandler; /* default SIGILL handler */ Handler sigSegvHandler; /* default SIGSEGV handler */ Handler sigBusHandler; /* default SIGBUS handler */ } Context; extern Context * pC; extern Class Throwable; #define except_class_declare(child, parent) extern Class child #define except_class_define(child, parent) Class child = { 1, parent, #child } except_class_declare(Exception, Throwable); except_class_declare(OutOfMemoryError, Exception); except_class_declare(FailedAssertion, Exception); except_class_declare(RuntimeException, Exception); except_class_declare(AbnormalTermination, RuntimeException); /* SIGABRT */ except_class_declare(ArithmeticException, RuntimeException); /* SIGFPE */ except_class_declare(IllegalInstruction, RuntimeException); /* SIGILL */ except_class_declare(SegmentationFault, RuntimeException); /* SIGSEGV */ except_class_declare(BusError, RuntimeException); /* SIGBUS */ #ifdef DEBUG #define CHECKED \ static int checked #define CHECK_BEGIN(pC, pChecked, file, line) \ ExceptCheckBegin(pC, pChecked, file, line) #define CHECK(pC, pChecked, class, file, line) \ ExceptCheck(pC, pChecked, class, file, line) #define CHECK_END \ !checked #else /* DEBUG */ #define CHECKED #define CHECK_BEGIN(pC, pChecked, file, line) 1 #define CHECK(pC, pChecked, class, file, line) 1 #define CHECK_END 0 #endif /* DEBUG */ #define except_thread_cleanup(id) ExceptThreadCleanup(id) #define try \ ExceptTry(pC, __FILE__, __LINE__); \ while (1) \ { \ Context * pTmpC = ExceptGetContext(pC); \ Context * pC = pTmpC; \ CHECKED; \ \ if (CHECK_BEGIN(pC, &checked, __FILE__, __LINE__) && \ pC->pEx->ready && SETJMP(pC->pEx->throwBuf) == 0) \ { \ pC->pEx->scope = TRY; \ do \ { #define catch(class, e) \ } \ while (0); \ } \ else if (CHECK(pC, &checked, class, __FILE__, __LINE__) && \ pC->pEx->ready && ExceptCatch(pC, class)) \ { \ Except *e = LifoPeek(pC->exStack, 1); \ pC->pEx->scope = CATCH; \ do \ { #define finally \ } \ while (0); \ } \ if (CHECK_END) \ continue; \ if (!pC->pEx->ready && SETJMP(pC->pEx->finalBuf) == 0) \ pC->pEx->ready = 1; \ else \ break; \ } \ ExceptGetContext(pC)->pEx->scope = FINALLY; \ while (ExceptGetContext(pC)->pEx->ready > 0 || ExceptFinally(pC)) \ while (ExceptGetContext(pC)->pEx->ready-- > 0) #define throw(pExceptOrClass, pData) \ ExceptThrow(pC, (ClassRef)pExceptOrClass, pData, __FILE__, __LINE__) #define return(x) \ { \ if (ExceptGetScope(pC) != OUTSIDE) \ { \ void * pData = malloc(sizeof(JMP_BUF)); \ ExceptGetContext(pC)->pEx->pData = pData; \ if (SETJMP(*(JMP_BUF *)pData) == 0) \ ExceptReturn(pC); \ else \ free(pData); \ } \ return x; \ } #define pending \ (ExceptGetContext(pC)->pEx->state == PENDING) extern Scope ExceptGetScope(Context *pC); extern Context *ExceptGetContext(Context *pC); extern void ExceptThreadCleanup(int threadId); extern void ExceptTry(Context *pC, char *file, int line); extern void ExceptThrow(Context *pC, void * pExceptOrClass, void *pData, char *file, int line); extern int ExceptCatch(Context *pC, ClassRef class); extern int ExceptFinally(Context *pC); extern void ExceptReturn(Context *pC); extern int ExceptCheckBegin(Context *pC, int *pChecked, char *file, int line); extern int ExceptCheck(Context *pC, int *pChecked, ClassRef class, char *file, int line); #endif /* _EXCEPT_H */ 

También hay un módulo C que contiene la lógica para el manejo de la señal y algo de contabilidad.

Fue extremadamente difícil de implementar, te lo puedo decir y casi renuncio. Realmente presioné para que fuera lo más cerca posible de Java; Me sorprendió lo lejos que llegué con solo C.

Dame un grito si te interesa.