setjmp.h es un encabezado definido en la biblioteca estándar de C para proporcionar "saltos no locales": control de flujo que se desvía de la llamada a subrutina habitual y la secuencia de retorno. Las funciones complementarias setjmp
y longjmp
proporcionan esta funcionalidad.
Un uso típico de setjmp
/longjmp
es la implementación de un mecanismo de excepción que utiliza la capacidad de longjmp
para restablecer el estado del programa o hilo, incluso a través de múltiples niveles de las llamadas a funciones. Un uso menos común de setjmp
es crear una sintaxis similar a corrutinas.
int setjmp(jmp_buf env) |
Establece el buffer jmp_buf local y lo inicializa para el salto. Esta rutina[1] guarda el contexto de la llamada del programa en el búfer de contexto especificado por el argumento env para su uso posterior por longjmp . Si el retorno es de una invocación directa, setjmp devuelve 0. Si el retorno es de una llamada a longjmp , setjmp devuelve un valor distinto de cero.
|
void longjmp(jmp_buf env, int value) |
Restaura el contexto del buffer de entorno env que se salvó por invocación de la rutina[1] en la misma invocación del programa. Invocando longjmp desde un manejador de señales anidado no definido. El valor especificado por valor es pasado desde longjmp a setjmp . Después de que longjmp se completa, la ejecución del programa continúa como si la invocación correspondiente de setjmp acabara de regresar. Si el valor pasado a longjmp es 0, setjmp se comportará como si hubiera devuelto 1;, de lo contrario, se comportará como si hubiera devuelto un valor .
|
setjmp
guarda el contexto actual (Ej., el estado del programa) en algún punto de la ejecución del programa, en una estructura de datos específica de la plataforma (jmp_buf
) que puede ser usada, en un punto más lejano de la ejecución del programa, por longjmp
para restaurar el estado del programa que fue guardado por setjmp
en jmp_buf
. Este proceso puede representarse como un "salto" atrás hacia el punto de la ejecución del programa donde setjmp
guardo el contexto. El (aparente) valor de retorno de setjmp
indica si el control alcanzado el punto de normalidad o de una llamada a longjmp
. Esto lleva a un común idioma: if( setjmp(x) ){/* manejar longjmp(x) */}
.
POSIX.1 no especifica si setjmp
y longjmp
guardan o restauran el conjunto actual de bloqueo de señales — si un programa cuenta con manejo de señales que debería usarlas instrucciones POSIX sigsetjmp
/siglongjmp
.
jmp_buf |
Un tipo de array, como struct __jmp_buf_tag[1] ,[2] apropiado para manejar la información necesaria para restaurar el contexto desde una llamada a sistema.
|
Cuando un "goto no-local" se ejecuta a través de setjmp / longjmp, el "desenrolle de pila" normal no ocurre y por lo tanto, las acciones de limpieza necesarios, tales como el cierre de descriptores de archivos, el vaciado de los búferes, la liberación de la memoria asignada en el heap, etc., no ocurren.
Si la función en la que fue llamada setjmp retorna, ya no es posible utilizar de forma segura longjmp con el objeto jmp_buf correspondiente. Esto es así porque el marco de pila se invalida cuando se devuelve la función. Llamar a longjmp restablece el puntero de pila, que -ya que la función retorna- apuntaría a un marco de pila inexistente y posiblemente sobrescrito o dañado.[3][4]
Del mismo modo, C99 no requiere que longjmp preserve el marco de pila actual. Esto significa que saltar a una función que sale a través de una llamada a longjmp no está definido.[5] Sin embargo, la mayoría de las implementaciones de longjmp dejan intacto el marco de pila, lo que permite a setjmp y longjmp ser utilizados para saltar hacia atrás y hacia adelante entre dos o más funciones -una característica explotada para la multitarea-.
En comparación con los mecanismos de lenguajes de programación de alto nivel como Python, Java, C + +, C #, e incluso lenguajes pre-C como Algol 60, la técnica de usar setjmp / longjmp para implementar un mecanismo de excepción es engorrosa. Estos lenguajes proporcionan técnicas de manejo de excepciones más potentes, mientras que lenguajes como Scheme, Smalltalk, y Haskell proporcionan construcciones de manejo de continuidad incluso más generales.
Este ejemplo muestra la idea básica de setjmp. La función main llama a first, la cual a su vez llama a second. La función "second" salta de vuelta a la función main, omitiendo la ejecución de la instrucción printf de la función "first".
#include <stdio.h>
#include <setjmp.h>
static jmp_buf buf;
void second(void) {
printf("second\n"); // Impresiones
longjmp(buf,1); // salta de nuevo a donde fue llamado setjmp
}
void first(void) {
second();
printf("first\n"); // No se Imprime
}
int main() {
if ( ! setjmp(buf) ) {
first(); // Cuando se ejecuta, devuelve 0 setjmp
} else { // cuando salta longjmp, setjmp devuelve 1
printf("main\n"); // Impresiones
}
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <setjmp.h>
void first(void);
void second(void);
/* This program's output is:
calling first
calling second
entering second
second failed with type 3 exception; remapping to type 1.
first failed, exception type 1
*/
/* Use a file scoped static variable for the exception stack so we can access
* it anywhere within this translation unit. */
static jmp_buf exception_env;
static int exception_type;
int main() {
void *volatile mem_buffer;
mem_buffer = NULL;
if (setjmp(exception_env)) {
/* if we get here there was an exception */
printf("first failed, exception type %d\n", exception_type);
} else {
/* Run code that may signal failure via longjmp. */
printf("calling first\n");
first();
mem_buffer = malloc(300); /* allocate a resource */
printf(strcpy((char*) mem_buffer, "first succeeded!")); /* ... this will not happen */
}
if (mem_buffer)
free((void*) mem_buffer); /* carefully deallocate resource */
return 0;
}
void first(void) {
jmp_buf my_env;
printf("calling second\n");
memcpy(my_env, exception_env, sizeof(jmp_buf));
switch (setjmp(exception_env)) {
case 3:
/* if we get here there was an exception. */
printf("second failed with type 3 exception; remapping to type 1.\n");
exception_type = 1;
default: /* fall through */
memcpy(exception_env, my_env, sizeof(jmp_buf)); /* restore exception stack */
longjmp(exception_env, exception_type); /* continue handling the exception */
case 0:
/* normal, desired operation */
second();
printf("second succeeded\n"); /* not reached */
}
memcpy(exception_env, my_env, sizeof(jmp_buf)); /* restore exception stack */
}
void second(void) {
printf("entering second\n" ); /* reached */
exception_type = 3;
longjmp(exception_env, exception_type); /* declare that the program has failed */
printf("leaving second\n"); /* not reached */
}
#include <setjmp.h>
#include <stdio.h>
jmp_buf mainTask, childTask;
void call_with_cushion(void);
void child(void);
int main(void) {
if (!setjmp(mainTask)) {
call_with_cushion(); /* child never returns */ /* yield */
} /* execution resumes after this "}" after first time that child yields */
for (;;) {
printf("Parent\n");
if (!setjmp(mainTask)) {
longjmp(childTask, 1); /* yield - note that this is undefined under C99 */
}
}
}
void call_with_cushion (void) {
char space[1000]; /* Reserve enough space for main to run */
space[999] = 1; /* Do not optimize array out of existence */
child();
}
void child (void) {
for (;;) {
printf("Child loop begin\n");
if (!setjmp(childTask)) longjmp(mainTask, 1); /* yield - invalidates childTask in C99 */
printf("Child loop end\n");
if (!setjmp(childTask)) longjmp(mainTask, 1); /* yield - invalidates childTask in C99 */
}
/* Don't return. Instead we should set a flag to indicate that main()
should stop yielding to us and then longjmp(mainTask, 1) */
}
setjmp
debe ser implementado como una macro, pero POSIX establece explícitamente que no está definido si setjmp
es una macro o una función.