setjmp.h es un encabezado definido en la biblioteca estándar de C para proporcionar "saltos no locales": flujo de control que se desvía de la secuencia habitual de llamada y retorno de subrutinasetjmp
. Las funciones complementarias y longjmp
proporcionan esta funcionalidad.
Un uso típico de setjmp
/ longjmp
es la implementación de un mecanismo de excepción que aprovecha la capacidad de longjmp
para restablecer el estado del programa o del subproceso, incluso en múltiples niveles de llamadas de función. Un uso menos común de setjmp
es crear una sintaxis similar a las corrutinas .
setjmp
guarda el entorno actual (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 se puede usar en algún punto posterior de la ejecución del programa para longjmp
restaurar el estado del programa al que guardó setjmp
into jmp_buf
. Este proceso se puede imaginar como un "salto" de regreso al punto de ejecución del programa donde setjmp
guardó el entorno. El valor de retorno (aparente) de setjmp
indica si el control llegó a ese punto normalmente (cero) o desde una llamada a longjmp
(distinto de cero). Esto conduce a un modismo común : .if( setjmp(x) ){/* handle longjmp(x) */}
POSIX .1 no especifica si se debe guardar setjmp
y longjmp
restaurar el conjunto actual de señales bloqueadas ; si un programa emplea manejo de señales, debe utilizar sigsetjmp
/ de POSIX siglongjmp
.
El fundamento C99 lo describe jmp_buf
como un tipo de matriz para compatibilidad con versiones anteriores ; el código existente se refiere a jmp_buf
las ubicaciones de almacenamiento por nombre (sin el &
operador de dirección), lo que solo es posible para los tipos de matriz. [2] Señala que simplemente puede ser una matriz de un solo miembro con su único miembro siendo los datos reales; de hecho, este es el enfoque empleado por la biblioteca GNU C , que define el tipo como struct __jmp_buf_tag[1]
.
Cuando se ejecuta un "goto no local" a través de setjmp
/ longjmp
en C++ , no se produce el " desenrollado de pila " normal. Por lo tanto, tampoco se realizarán las acciones de limpieza necesarias. Esto podría incluir el cierre de descriptores de archivos , el vaciado de búferes o la liberación de memoria asignada al montón .
Si la función en la que setjmp
se llamó retorna, ya no es posible usarla de manera segura longjmp
con el jmp_buf
objeto correspondiente. Esto se debe a que el marco de pila se invalida cuando la función retorna. La llamada longjmp
restaura el puntero de pila , que, debido a que la función retorna, apuntaría a un marco de pila inexistente y potencialmente sobrescrito o dañado. [3] [4]
De manera similar, C99 no exige que longjmp
se conserve el marco de pila actual. Esto significa que saltar a una función de la que se salió mediante una llamada a longjmp
no está definido. [5]
El ejemplo siguiente muestra la idea básica de setjmp. Allí, main()
llama a first()
, que a su vez llama a second()
. Luego, second()
vuelve a saltar a main()
, saltando first()
la llamada de printf()
.
#incluir <stdio.h> #incluir <setjmp.h> estático jmp_buf buf ; void second () { printf ( "second \n " ); // imprime longjmp ( buf , 1 ); // salta de nuevo al lugar donde se llamó a setjmp - haciendo que setjmp ahora devuelva 1 } void primero () { segundo (); printf ( "primero \n " ); // no imprime } int main () { if ( ! setjmp ( buf )) first (); // cuando se ejecuta, setjmp devuelve 0 else // cuando longjmp salta hacia atrás, setjmp devuelve 1 printf ( "main \n " ); // imprime devuelve 0 ; }
Al ejecutarse, el programa anterior generará el siguiente resultado:
segundoprincipal
Tenga en cuenta que, aunque first()
se llama a la subrutina, " first
" nunca se imprime, ya que second()
nunca devuelve el control a first()
. En cambio, " " se imprime cuando se verifica main
la declaración condicional una segunda vez.if (!setjmp(buf))
En este ejemplo, setjmp
se utiliza para incluir el manejo de excepciones, como tryen otros lenguajes. La llamada a longjmp
es análoga a una throw
declaración, lo que permite que una excepción devuelva un estado de error directamente a setjmp
. El siguiente código se adhiere al estándar ISO C de 1999 y a la Especificación Única de UNIX al invocarlo setjmp
en un rango limitado de contextos: [6]
if
declaración switch
de iteración o!
o una comparación con una constante entera.El cumplimiento de estas reglas puede facilitar la implementación para crear el búfer de entorno, lo que puede ser una operación delicada. [2] El uso más general de setjmp
puede causar un comportamiento indefinido, como la corrupción de variables locales; los compiladores y entornos conformes no están obligados a proteger o incluso advertir contra dicho uso. Sin embargo, expresiones idiomáticas ligeramente más sofisticadas como switch ((exception_type = setjmp(env))) { }
son comunes en la literatura y la práctica, y siguen siendo relativamente portables. A continuación se presenta una metodología de conformidad simple, donde se mantiene una variable adicional junto con el búfer de estado. Esta variable podría elaborarse en una estructura que incorpore el propio búfer.
En un ejemplo de apariencia más moderna, el bloque "try" habitual se implementaría como un setjmp (con algún código de preparación para saltos multinivel, como se ve en first
), el "throw" como longjmp con el parámetro opcional como excepción y el "catch" como el bloque "else" bajo "try".
#include <setjmp.h> #include <stdio.h> #include <stdlib.h> #include <string.h> void estático primero (); void estático segundo (); /* Utilice una variable estática con ámbito de archivo para la pila de excepciones de modo que podamos acceder a ella * en cualquier lugar dentro de esta unidad de traducción. */ static jmp_buf exception_env ; static int exception_type ; int main ( void ) { char * volátil mem_buffer = NULL ; if ( setjmp ( exception_env )) { // si llegamos aquí hubo una excepción printf ( "first falló, tipo de excepción: %d \n " , exception_type ); } else { // Ejecutar código que puede señalar una falla a través de longjmp. puts ( "calling first" ); first (); mem_buffer = malloc ( 300 ); // asignar un recurso printf ( "%s \n " , strcpy ( mem_buffer , "primer éxito" )); // no alcanzado } free ( mem_buffer ); // Se puede pasar NULL a free, no se realiza ninguna operación devuelve 0 ; } vacío estático primero () { jmp_buf my_env ; pone ( "entrando primero" ); // alcanzado memcpy ( my_env , exception_env , sizeof my_env ); // almacena el valor de exception_env en my_env ya que exception_env será reutilizado switch ( setjmp ( exception_env )) { caso 3 : // si llegamos aquí hubo una excepción. puts ( "segundo error, tipo de excepción: 3; reasignando al tipo 1" ); exception_type = 1 ; predeterminado : // pasar por memcpy ( exception_env , my_env , sizeof exception_env ); // restaurar la pila de excepciones longjmp ( exception_env , exception_type ); // continuar manejando la excepción caso 0 : // operación normal, deseada puts ( "llamando segundo" ); // alcanzado segundo (); puts ( "segundo exitoso" ); // no alcanzado } memcpy ( exception_env , my_env , sizeof exception_env ); // restaurar la pila de excepciones pone ( "saliendo primero" ); // nunca alcanzado } static void second () { puts ( "ingresando segundo" ); // alcanzado exception_type = 3 ; longjmp ( exception_env , exception_type ); // declara que el programa ha fallado pone ( "saliendo segundo" ); // no alcanzado }
El resultado de este programa es:
llamando primeroentrando primerollamando segundoentrando segundoSegundo error, tipo de excepción: 3; reasignación al tipo 1primer error, tipo de excepción: 1
C99 establece que longjmp
se garantiza que funcione solo cuando el destino es una función que realiza la llamada, es decir, que se garantiza que el ámbito de destino esté intacto. Saltar a una función que ya ha finalizado por return
o longjmp
no está definida. [5] Sin embargo, la mayoría de las implementaciones de longjmp
no destruyen específicamente las variables locales al realizar el salto. Dado que el contexto sobrevive hasta que se borran sus variables locales, en realidad podría restaurarse mediante setjmp
. En muchos entornos (como Really Simple Threads y TinyTimbers), expresiones idiomáticas como if(!setjmp(child_env)) longjmp(caller_env);
pueden permitir que una función llamada pause y reanude de manera efectiva en un setjmp
.
Las bibliotecas de subprocesos aprovechan esto para proporcionar funciones de multitarea cooperativa sin utilizar setcontext
otras funciones de fibra .
Teniendo en cuenta que setjmp
una función secundaria generalmente funcionará a menos que sea saboteada y que setcontext
, como parte de POSIX, no es necesario que la proporcionen las implementaciones de C, este mecanismo puede ser portátil cuando la setcontext
alternativa falla.
Dado que no se generará ninguna excepción en caso de desbordamiento de una de las múltiples pilas de un mecanismo de este tipo, es esencial sobreestimar el espacio necesario para cada contexto, incluido el que contiene main()
e incluye espacio para cualquier manejador de señales que pueda interrumpir la ejecución normal. Si se excede el espacio asignado, se dañarán los demás contextos, generalmente con las funciones más externas primero. Desafortunadamente, los sistemas que requieren este tipo de estrategia de programación también suelen ser pequeños y con recursos limitados.
#incluir <setjmp.h> #incluir <stdio.h> jmp_buf tarea principal , tarea secundaria ; void llamada_con_cojin (); void hijo (); int main () { if ( ! setjmp ( mainTask )) { call_with_cushion (); // el hijo nunca retorna, cede } // la ejecución se reanuda después de este "}" después de la primera vez que el hijo cede mientras ( 1 ) { printf ( "Padre \n " ); si ( ! setjmp ( mainTask )) longjmp ( childTask , 1 ); // rendimiento - tenga en cuenta que esto no está definido en C99 } } void call_with_cushion () { char space [ 1000 ]; // Reserva suficiente espacio para que se ejecute main space [ 999 ] = 1 ; // No optimice la matriz hasta que exista child (); } void child () { while ( 1 ) { printf ( "El bucle secundario comienza \n " ); if ( ! setjmp ( childTask )) longjmp ( mainTask , 1 ); // yield - invalida childTask en C99 printf ( "Fin del bucle secundario \n " ); if ( ! setjmp ( childTask )) longjmp ( mainTask , 1 ); // yield - invalida childTask en C99 } /* No retornar. En su lugar, deberíamos establecer un indicador para indicar que main() debe dejar de cedernos el paso y luego longjmp(mainTask, 1) */ }
setjmp
debe implementarse como una macro, pero POSIX establece explícitamente que no está definido si setjmp
es una macro o una función.