<setjmp.h> es un archivo de cabecera definido en la biblioteca estándar de C para proporcionar "saltos no locales": un flujo de control que se desvía de la secuencia habitual de llamada y retorno de subrutinas . Las funciones complementariassetjmpproporcionanlongjmpesta funcionalidad.
Un uso típico de setjmp/ longjmpes la implementación de un mecanismo de excepciones que aprovecha la capacidad de longjmprestablecer el estado del programa o hilo, incluso a través de múltiples niveles de llamadas a funciones. Un uso menos común de setjmpes crear una sintaxis similar a las corrutinas .
Funciones de los miembros
intsetjmp(jmp_bufenv)- Configura el
jmp_bufbúfer local y lo inicializa para el salto. Esta rutina [ 1 ] guarda el entorno de llamada del programa en el búfer de entorno especificado por elenvargumento para su uso posterior porlongjmp. Si el retorno proviene de una invocación directa,setjmpdevuelve 0. Si el retorno proviene de una llamada alongjmp,setjmpdevuelve un valor distinto de cero. voidlongjmp(jmp_bufenv,intvalue)- Restaura el contexto del búfer de entorno
envque se guardó mediante la invocación de lasetjmprutina [ 1 ] en la misma invocación del programa. La invocaciónlongjmpdesde un manejador de señales anidado no está definida . El valor especificado porvaluese pasa delongjmpasetjmp. Después delongjmpque se completa, la ejecución del programa continúa como si la invocación correspondiente desetjmphubiera devuelto . Si elvaluepasado alongjmpes 0,setjmpse comportará como si hubiera devuelto 1; de lo contrario, se comportará como si hubiera devueltovalue.
setjmpguarda 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 puede ser utilizada en algún punto posterior de la ejecución del programa por longjmppara restaurar el estado del programa al que fue guardado por setjmpen jmp_buf. Este proceso puede imaginarse como un "salto" de regreso al punto de ejecución del programa donde setjmpse guardó el entorno. El valor de retorno (aparente) de setjmpindica si el control llegó a ese punto normalmente (cero) o a partir de una llamada a longjmp(distinto de cero). Esto lleva a un modismo común : .if(setjmp(x)){/* handle longjmp(x) */}
POSIX .1 no especifica si guarda setjmpy longjmprestaura el conjunto actual de señales bloqueadas ; si un programa emplea manejo de señales, debe usar sigsetjmp/ de POSIX siglongjmp.
Tipos de miembros
jmp_buf- Un tipo de matriz adecuado para almacenar la información necesaria para restaurar un entorno de llamada.
La justificación de C99 lo describe jmp_bufcomo un tipo de matriz para compatibilidad con versiones anteriores ; el código existente se refiere a jmp_bufubicaciones de almacenamiento por nombre (sin el &operador de dirección), lo cual solo es posible para tipos de matriz. [ 2 ] Señala que puede ser simplemente una matriz de un solo miembro, donde su único miembro es el dato real; de hecho, este es el enfoque empleado por la biblioteca GNU C , que define el tipo como struct __jmp_buf_tag[1].
Advertencias y limitaciones
Cuando se ejecuta una instrucción "goto no local" mediante setjmp/ longjmpen C++ , no se produce el desenrollado normal de la pila. Por lo tanto, tampoco se realizarán las acciones de limpieza necesarias. Esto podría incluir el cierre de descriptores de archivo , el vaciado de búferes o la liberación de memoria asignada en el montón .
Si la función en la que setjmpse llamó regresa, ya no es posible usarla de forma segura longjmpcon el jmp_bufobjeto correspondiente. Esto se debe a que el marco de pila se invalida cuando la función regresa. Llamar longjmpa restaura el puntero de pila , que, debido a que la función regresó, apuntaría a un marco de pila inexistente y potencialmente sobrescrito o corrupto. [ 3 ] [ 4 ]
De manera similar, C99 no requiere que longjmpse conserve el marco de pila actual. Esto significa que saltar a una función que se salió mediante una llamada a longjmpno está definido. [ 5 ]
Ejemplo de uso
Ejemplo sencillo
El siguiente ejemplo muestra la idea básica de setjmp. Allí, main()llama a first(), que a su vez llama a second(). Luego, second()vuelve a entrar en main(), omitiendo first()la llamada de printf().
#include <setjmp.h> #include <stdio.h>estático jmp_buf buf ;void second () { printf ( "second \n " ); // imprime longjmp ( buf , 1 ); // regresa al punto donde se llamó a setjmp, haciendo que setjmp ahora devuelva 1 }void first () { second (); printf ( "first \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 }devolver 0 ; }Al ejecutarse, el programa anterior mostrará lo siguiente:
segundo principalNótese que, aunque first()se llama a la subrutina, " first" nunca se imprime, ya que second()nunca devuelve el control a first(). En cambio, " main" se imprime cuando if (!setjmp(buf))se comprueba la instrucción condicional por segunda vez.
Manejo de excepciones
En este ejemplo, setjmpse utiliza para delimitar el manejo de excepciones, como tryen otros lenguajes. La llamada a longjmpes análoga a una throwinstrucció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 invocar setjmpen un rango limitado de contextos: [ 6 ]
- Como condición para una instrucción de iteración o
ifiteraciónswitch - Como se indicó anteriormente, en conjunto con una sola
!comparación con una constante entera. - Como una instrucción (con el valor de retorno sin usar)
Seguir estas reglas puede facilitar a la implementación la creación del búfer de entorno, que puede ser una operación delicada. [ 2 ] Un uso más generalizado setjmppuede causar un comportamiento indefinido, como la corrupción de variables locales; los compiladores y entornos conformes no están obligados a proteger ni siquiera a advertir sobre dicho uso. Sin embargo, modismos ligeramente más sofisticados como son comunes en la literatura y la práctica, y siguen siendo relativamente portátiles. A continuación se presenta una metodología conforme 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.switch((exception_type=setjmp(env))){}
En un ejemplo de aspecto más moderno, 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" debajo de "try".
#include <setjmp.h> #include <stdio.h> #include <stdlib.h> #include <string.h>static void first (); static void second ();/* Usamos una variable estática con ámbito de archivo para la pila de excepciones para poder 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 ) { volatile char * 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 fallas a través de longjmp. puts ( "llamando a first" ); first ();mem_buffer = ( char * ) malloc ( 300 ); // asignar un recurso printf ( "%s \n " , strcpy ( mem_buffer , "first succeeded" )); // no alcanzado }free ( mem_buffer ); // Se puede pasar NULL a free, no se realiza ninguna operacióndevolver 0 ; }static void first () { jmp_buf my_env ;puts ( "entrando primero" ); // alcanzadomemcpy ( my_env , exception_env , sizeof my_env ); // Almacenar el valor de exception_env en my_env ya que exception_env se reutilizaráswitch ( setjmp ( exception_env )) { case 3 : // si llegamos aquí hubo una excepción. printf ( "segundo falló, tipo de excepción: 3; reasignando al tipo 1" ); exception_type = 1 ;default : // continuar con memcpy ( exception_env , my_env , sizeof exception_env ); // restaurar la pila de excepciones longjmp ( exception_env , exception_type ); // continuar manejando la excepcióncaso 0 : // operación normal y deseada printf ( "llamando a second" ); // se alcanzó second (); printf ( "second tuvo éxito" ); // no se alcanzó }memcpy ( exception_env , my_env , sizeof exception_env ); // restaurar la pila de excepcionesputs ( "saliendo primero" ); // nunca se llegó }static void second () { printf ( "entrando en segundo" ); // alcanzadoexception_type = 3 ; longjmp ( exception_env , exception_type ); // declara que el programa ha falladoprintf ( "Saliendo segundo" ); // no alcanzado }El resultado de este programa es:
La llamada a la primera entrada a la primera llamada a la segunda entrada a la segunda falló, tipo de excepción: 3; la reasignación al tipo 1 falló, tipo de excepción: 1multitarea cooperativa
C99 proporciona que longjmpse 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 terminado por returno longjmpno está definido. [ 5 ] Sin embargo, la mayoría de las implementaciones de longjmpno 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 ), modismos como pueden permitir que una función llamada se pause y reanude efectivamente en un .if(!setjmp(child_env))longjmp(caller_env);setjmp
Esto es aprovechado por las bibliotecas de subprocesos para proporcionar funcionalidades de multitarea cooperativa sin utilizar setcontextotras funcionalidades de fibra .
Considerando que setjmpuna función hija generalmente funcionará a menos que sea saboteada, y que setcontext, como parte de POSIX, no es obligatorio que las implementaciones de C lo proporcionen, este mecanismo puede ser portable cuando la setcontextalternativa falla.
Dado que en este mecanismo no se genera ninguna excepción al desbordarse una de las múltiples pilas, es fundamental sobreestimar el espacio necesario para cada contexto, incluido el que contiene main()y abarca el espacio para cualquier manejador de señales que pueda interrumpir la ejecución normal. Si se excede el espacio asignado, los demás contextos se verán afectados, generalmente comenzando por las funciones más externas. Desafortunadamente, los sistemas que requieren este tipo de estrategia de programación suelen ser pequeños y con recursos limitados.
#include <setjmp.h> #include <stdio.h>jmp_buf tarea principal , tarea secundaria ;void call_with_cushion (); void child ();int main () { if ( ! setjmp ( mainTask )) { call_with_cushion (); // el hijo nunca regresa, yield } // la ejecución se reanuda después de esta "}" después de la primera vez que el hijo cedewhile ( true ) { printf ( "Padre \n " ); if ( ! setjmp ( mainTask )) { longjmp ( childTask , 1 ); // yield - 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 optimizar el array hasta que no exista child (); }void child () { while ( true ) { printf ( "Comienza el bucle del hijo \n " ); if ( ! setjmp ( childTask )) { longjmp ( mainTask , 1 ); // yield - invalida childTask en C99 }printf ( "Fin del bucle hijo \n " );if ( ! setjmp ( childTask )) { longjmp ( mainTask , 1 ); // yield - invalida childTask en C99 } }/* No retornamos. En su lugar, deberíamos establecer una bandera para indicar que main() debe dejar de cedernos el control y luego longjmp(mainTask, 1) */ }Referencias
- 1 2 ISO C establece que
setjmpdebe implementarse como una macro, pero POSIX establece explícitamente que no está definido sisetjmpes una macro o una función. - 1 2 C99 Fundamento, versión 5.10, abril de 2003 , sección 7.13
- ↑ Apuntes de clase de CS360 — Setjmp y Longjmp
- ↑ setjmp(3) Archivado el 26/07/2009 en Wayback Machine
- 1 2 ISO/IEC 9899:1999 , 2005, 7.13.2.1:2 y nota al pie 211
- ↑ : establecer punto de salto para un goto no local – Referencia de interfaces del sistema, Especificación UNIX única , versión 5 de The Open Group
Lecturas adicionales
- Nidito, Francesco (2 de julio de 2016). "Excepciones en C con Longjmp y Setjmp" . Groups.Di.Unipi.it . Recuperado el 2 de enero de 2024 .
Enlaces externos
- : establecer punto de salto para un goto no local – Referencia de interfaces del sistema, Especificación UNIX única , versión 5 de The Open Group
- ¿Existen nuevamente las funciones sigsetjmp/siglongjmp? (Acerca de estas funciones en mingw / MSYS )
- Encabezados de la biblioteca estándar de C
- Flujo de control