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 subrutina . 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 explota la capacidad de longjmp
restablecer el estado del programa o del hilo, incluso a través de múltiples niveles de llamadas a funciones. Un uso menos común de setjmp
es crear una sintaxis similar a las corrutinas .
Funciones de los miembros
int setjmp(jmp_buf env) | Configura el jmp_buf bú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 el env argumento para su uso posterior longjmp . Si la devolución es de una invocación directa, setjmp devuelve 0. Si la devolución es de una llamada a longjmp , setjmp devuelve un valor distinto de cero. |
void longjmp(jmp_buf env, int value) | Restaura el contexto del búfer de entorno env que se guardó mediante la invocación de la setjmp rutina [1] en la misma invocación del programa. La invocación longjmp desde un manejador de señales anidado no está definida . El valor especificado por value se pasa de longjmp a setjmp . Una vez longjmp completado, la ejecución del programa continúa como si la invocación correspondiente de setjmp acabara de regresar. Si el value paso a longjmp es 0, setjmp se comportará como si hubiera devuelto 1; de lo contrario, se comportará como si hubiera regresado value . |
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 utilizar en algún punto posterior de la ejecución del programa longjmp
para restaurar el estado del programa al guardado por setjmp
en jmp_buf
. Este proceso se puede imaginar como un "salto" de regreso al punto de ejecución del programa donde se setjmp
salvó el entorno. El valor de retorno (aparente) de setjmp
indica si el control alcanzó ese punto normalmente (cero) o de una llamada a longjmp
(distinto de cero). Esto conduce a un común lenguaje : .if( setjmp(x) ){/* handle longjmp(x) */}
POSIX 0.1 no especifica si setjmp
y longjmp
guardar y restaurar el conjunto actual de bloqueados señales ; si un programa emplea manejo de señales, debería usar POSIX sigsetjmp
/ siglongjmp
.
Tipos de miembros
jmp_buf | Un tipo de matriz, tal como struct __jmp_buf_tag[1] , [2] adecuada para contener la información necesaria para restaurar un entorno de la llamada. |
El fundamento C99 se 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 de), que solo es posible para los tipos de arreglos. [3]
Advertencias y limitaciones
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 producirán las acciones de limpieza necesarias. Esto podría incluir cerrar descriptores de archivos , vaciar búferes o liberar memoria asignada al montón .
Si la función en la que setjmp
se llamó regresa, ya no es posible usarla de manera segura longjmp
con el jmp_buf
objeto correspondiente . Esto se debe a que el marco de la pila se invalida cuando la función regresa. La llamada longjmp
restaura el puntero de la pila , que, debido a que la función regresó, apuntaría a un marco de pila inexistente y potencialmente sobrescrito o dañado. [4] [5]
De manera similar, C99 no requiere que se longjmp
preserve 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. [6] Sin embargo, la mayoría de las implementaciones longjmp
dejan intacto el marco de la pila, lo que permite que setjmp
y longjmp
se use para saltar de un lado a otro entre dos o más funciones, una característica que se aprovecha para realizar múltiples tareas .
En comparación con los mecanismos en 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 poderosas , mientras que lenguajes como Scheme , Smalltalk y Haskell proporcionan construcciones de manejo de continuación aún más generales .
Uso de ejemplo
Ejemplo simple
El siguiente ejemplo muestra la idea básica de setjmp. Allí, main()
llama first()
, que a su vez llama second()
. Luego, second()
vuelve a main()
saltar, saltando first()
la llamada de printf()
.
#include #include static jmp_buf buf ;anular segundo () { printf ( "segundo \ n " ); // imprime longjmp ( buf , 1 ); // regresa a donde se llamó setjmp - haciendo que setjmp ahora regrese 1 }anular primero () { segundo (); printf ( "primero \ n " ); // no imprime }int main () { if ( ! setjmp ( buf )) primero (); // cuando se ejecuta, setjmp devuelve 0 else // cuando longjmp retrocede, setjmp devuelve 1 printf ( "main \ n " ); // imprime return 0 ; }
Cuando se ejecuta, el programa anterior generará:
segundoprincipal
Observe que aunque first()
se llama a la subrutina, " first
" nunca se imprime. " main
" se imprime cuando la instrucción condicional if (!setjmp(buf))
se ejecuta por segunda vez.
Manejo de excepciones
En este ejemplo, setjmp
se usa para poner entre paréntesis el manejo de excepciones, como try
en algunos 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 una gama limitada de contextos: [7]
- Como condición para una declaración
if
,switch
o iteración - Como arriba junto con una única
!
o comparación con una constante entera - Como una declaración (con el valor de retorno sin usar)
Seguir estas reglas puede facilitar que la implementación cree el búfer de entorno, lo que puede ser una operación delicada. [3] 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, modismos ligeramente más sofisticados, como los que switch ((exception_type = setjmp(env))) { }
son comunes en la literatura y la práctica, siguen siendo relativamente portátiles. 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 búfer en sí.
En un ejemplo de aspecto más moderno, el bloque "try" habitual se implementaría como 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 #include #include #include vacío estático primero (); segundo vacío estático (); / * Use una variable estática con alcance de archivo para la pila de excepciones para 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 * volatile mem_buffer = NULL ; if ( setjmp ( exception_env )) { // si llegamos aquí hubo una excepción printf ( "primer error, tipo de excepción:% d \ n " , exception_type ); } else { // Ejecuta el código que puede indicar una falla a través de longjmp. pone ( "llamando primero" ); primero (); mem_buffer = malloc ( 300 ); // asigna un recurso printf ( "% s \ n " , strcpy ( mem_buffer , "primero tuvo éxito" )); // no alcanzado } libre ( mem_buffer ); // NULL se puede pasar a free, no se realiza ninguna operación return 0 ; } vacío estático primero () { jmp_buf my_env ; pone ( "entrando primero" ); // alcanzado memcpy ( my_env , exception_env , sizeof my_env ); switch ( setjmp ( exception_env )) { caso 3 : // si llegamos aquí hubo una excepción. put ( "segundo falló, tipo de excepción: 3; reasignación al tipo 1" ); tipo_excepción = 1 ; predeterminado : // pasar por memcpy ( exception_env , my_env , sizeof exception_env ); // restaura la pila de excepciones longjmp ( exception_env , exception_type ); // continuar manejando la excepción case 0 : // operación normal, deseada, put ( "llamando al segundo" ); // alcanzó el segundo (); put ( "segundo éxito" ); // no alcanzado } memcpy ( exception_env , my_env , sizeof exception_env ); // restaurar la pila de excepciones pone ( "saliendo primero" ); // nunca llegó }static void second () { pone ( "entrando en segundo lugar" ); // alcanzado tipo_excepción = 3 ; longjmp ( excepción_env , excepción_tipo ); // declara que el programa ha fallado pone ( "dejando en segundo lugar" ); // no alcanzado }
La salida de este programa es:
llamando primeroentrando primerollamando segundoentrando segundosegundo falló, tipo de excepción: 3; reasignación al tipo 1primer error, tipo de excepción: 1
Aunque la exception_type
variable técnicamente no es necesaria aquí ya que setjmp devuelve el valor distinto de cero con el que se llamó a longjmp (como en el segundo y el primero), en la práctica se usaría un objeto global más elaborado para acomodar excepciones más ricas.
En el mundo real, setjmp-longjmp (sjlj) solía ser la forma estándar de manejo de excepciones en compiladores de C ++ de Windows de terceros (es decir, MinGW ), ya que el Manejo de excepciones estructurado nativo está pobremente documentado en general y también fue patentado en 32- poco Windows hasta 2014.
Multitarea cooperativa
C99 establece que longjmp
se garantiza que funcionará solo cuando el destino es una función de llamada, es decir, que se garantiza que el alcance del destino está intacto. Saltar a una función que ya ha terminado return
o longjmp
no está definida. [6] 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 ser restaurado por setjmp
. En muchos entornos (como Really Simple Threads y TinyTimbers ), modismos 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 facilidades multitarea cooperativas sin utilizar setcontext
otras facilidades de fibra . Mientras que setcontext
es un servicio de biblioteca que puede crear un contexto de ejecución en la memoria asignada al montón y puede admitir otros servicios como la protección de desbordamiento del búfer , el programador implementa el abuso de [ cita requerida ]setjmp
, que puede reservar memoria en la pila y no notificar la biblioteca o sistema operativo del nuevo contexto operativo. Por otro lado, la implementación de una biblioteca setcontext
puede usarse internamente setjmp
de una manera similar a este ejemplo para guardar y restaurar un contexto, después de que se haya inicializado de alguna manera.
Teniendo en cuenta que setjmp
para una función secundaria generalmente funcionará a menos que sea saboteada y setcontext
, como parte de POSIX , no es necesario que la proporcionen las implementaciones de C, este mecanismo puede ser portátil donde la setcontext
alternativa falla.
Dado que no se generará ninguna excepción tras el desbordamiento de una de las múltiples pilas en dicho mecanismo, es esencial sobreestimar el espacio requerido para cada contexto, incluido el que contiene main()
e incluye espacio para cualquier controlador de señal que pueda interrumpir la ejecución regular. Exceder el espacio asignado dañará los otros contextos, generalmente con las funciones más externas primero. Desafortunadamente, los sistemas que requieren este tipo de estrategia de programación a menudo también son pequeños con recursos limitados.
#include #include jmp_buf mainTask , childTask ;void call_with_cushion (); niño vacío ();int main () { if ( ! setjmp ( mainTask )) { call_with_cushion (); // el niño nunca regresa, rendimiento } // la ejecución se reanuda después de este "}" después de la primera vez que el niño cede while ( 1 ) { printf ( "Padre \ n " ); if ( ! setjmp ( mainTask )) longjmp ( childTask , 1 ); // rendimiento: tenga en cuenta que esto no está definido en C99 } }void call_with_cushion () { Char espacio [ 1000 ]; // Reserva suficiente espacio para que main se ejecute space [ 999 ] = 1 ; // No optimizar la matriz fuera de existencia child (); }void child () { while ( 1 ) { printf ( "Child loop begin \ n " ); if ( ! setjmp ( childTask )) longjmp ( mainTask , 1 ); // rendimiento - invalida childTask en C99 printf ( "Fin del ciclo secundario \ n " ); if ( ! setjmp ( childTask )) longjmp ( mainTask , 1 ); // rendimiento - invalida childTask en C99 } / * No regreses. En su lugar, deberíamos establecer una bandera para indicar que main () debería dejar de ceder ante nosotros y luego longjmp (mainTask, 1) * / }
Ver también
- Continuación
- contexto
Referencias
- ^ a b ISO C establece que
setjmp
debe implementarse como una macro, pero POSIX establece explícitamente que no está definido sisetjmp
es una macro o una función. - ^ Este es el tipo utilizado por la biblioteca GNU C , versión 2.7
- ^ a b C99 Justificación, versión 5.10, abril de 2003 , sección 7.13
- ^ CS360 Lecture Notes - Setjmp y Longjmp
- ↑ setjmp (3) Archivado el 26 de julio de 2009 en la Wayback Machine.
- ^ a b 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 única de UNIX , Número 7 de The Open Group
enlaces externos
- : establecer el punto de salto para un goto no local - Referencia de interfaces del sistema, Especificación única de UNIX , Edición 7 de The Open Group
- Excepciones en C con Longjmp y Setjmp
- ¿Hay sigsetjmp / siglongjmp (de nuevo) (sobre estas funciones en mingw / MSYS )