signal - que es una biblioteca en c++
Uso práctico de setjmp y longjmp en C (7)
Dado que usted menciona incrustado, creo que vale la pena señalar un caso de no uso : cuando su norma de codificación 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".
¿Alguien me puede explicar dónde exactamente se pueden usar las funciones setjmp()
y longjmp()
prácticamente en la programación integrada? Sé que estos son para el manejo de errores. Pero me gustaría saber algunos casos de uso.
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 <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <setjmp.h>
#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.
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 bloqueos, 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 compilado 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.
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 bloqueos, 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 programa 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.
setjmp () y longjmp () son útiles para tratar los errores e interrupciones encontrados en una subrutina de bajo nivel de un programa.
setjmp
y longjmp
pueden ser muy útiles en las pruebas unitarias.
Supongamos que queremos probar el siguiente módulo:
#include <stdlib.h>
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 programa de prueba:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <setjmp.h>
// 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 programa y llama a _exit
para hacerlo. Si no hace esto, es posible que su programa de prueba no se cierre de forma limpia.
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 pila de llamadas (vea el comentario de MikeMB; aunque todavía no he tenido la oportunidad de verificarlo).
#include <stdio.h>
#include <setjmp.h>
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:
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 .