Acerca de setjmp/longjmp
linux x86 (5)
El puntero de la pila marca la división entre las porciones "usada" y "sin usar" de la pila. Cuando llama a setjmp
, todos los marcos de llamadas actuales están en el lado "usado", y todas las llamadas que tienen lugar después de setjmp
, pero antes de la función que llamó a setjmp
, tienen sus marcos de llamadas en el lado "no utilizado" del puntero de pila guardado . Tenga en cuenta que la invocación de longjmp
después de devolver la función que llamó a setjmp
invoca un comportamiento indefinido, por lo que no es necesario tener en cuenta ese caso.
Ahora, es posible que las variables locales en algunos de los marcos de llamadas existentes se modifiquen después de setjmp
, ya sea por la función de llamada o mediante punteros, y esta es una de las razones por las que es necesario usar volatile
en muchos casos ...
Estaba investigando setjmp / longjmp y descubrí que setjmp guarda registros como puntero de instrucción, puntero de pila, etc.
Sin embargo, lo que no entiendo aquí es que no se pueden modificar los datos en la pila del hilo entre la llamada a setjmp y longjmp . En ese caso, no funcionaría largamente como se esperaba.
Para dejarlo en claro, por ejemplo, cuando longjmp restaura el puntero de la pila, digamos que los datos en la memoria que señala el puntero de la pila ahora no son los mismos que cuando se llamó a setjmp . ¿Puede pasar esto? Y si eso sucede, ¿no estamos en problemas?
También lo que significa la declaración, " Las rutinas longjmp () no se pueden llamar después de que la rutina que llamó a las rutinas setjmp () regrese " .
En el siguiente ejemplo, setjmp / longjump altera el valor de i, que vive en main, a través de un puntero. Nunca se incrementa en el ciclo for. Para obtener más diversión, vea la entrada albert.c, http://www.ioccc.org/years-spoiler.html ganadora del IOCCC de 1992. (una de las pocas veces que ROTFLED leí una fuente C ...)
#include <stdio.h>
#include <setjmp.h>
jmp_buf the_state;
void helper(int *p);
int main (void)
{
int i;
for (i =0; i < 10; ) {
switch (setjmp (the_state) ) {
case 0: helper (&i) ; break;
case 1: printf( "Even=/t"); break;
case 2: printf( "Odd=/t"); break;
default: printf( "Oops=/t"); break;
}
printf( "I=%d/n", i);
}
return 0;
}
void helper(int *p)
{
*p += 1;
longjmp(the_state, 1+ *p%2);
}
La característica setjmp / longjmp (de aquí en adelante slj) en C es fea, y su comportamiento puede variar entre implementaciones. Sin embargo, dada la ausencia de excepciones, slj a veces es necesario en C (obsérvese que C ++ proporciona excepciones que son casi en todos los sentidos superiores a slj, y que slj interactúa mal con muchas características de C ++).
Al usar slj, uno debe tener en cuenta lo siguiente, asumiendo que la rutina Parent () llama a la rutina Setter (), que llama a setjmp () y luego llama a Jumper, que a su vez llama a longjmp ().
- El código puede salir legalmente del alcance en el que se realiza un setjmp sin que se haya ejecutado un longjmp; tan pronto como se cierre el alcance, sin embargo, el jmp_buf creado previamente debe considerarse no válido. El compilador probablemente no hará nada para marcarlo como tal, pero cualquier intento de usarlo puede generar un comportamiento impredecible, que probablemente incluya un salto a una dirección arbitraria.
- Cualquier variable local en Jumper () se evaporará con la llamada a longjmp (), haciendo que sus valores sean irrelevantes.
- Cuando el control regrese a Parent, a través de cualquier medio, las variables locales de Parent estarán como estaban cuando llamaron a Setter, a menos que se tomaran las direcciones de esas variables y se cambiasen usando dichos punteros; en cualquier caso, setjmp / longjmp no afectará sus valores de ninguna manera. Si dichas variables no tienen sus direcciones tomadas, es posible que setjmp () pueda almacenar en caché los valores de dichas variables y longjmp () puede restaurarlas. En ese escenario, sin embargo, no habría forma de que las variables cambien entre cuando se almacenan en caché y cuando se restauran, por lo que el caché / restauración no tendrá ningún efecto visible.
- Las variables en Setter pueden o no almacenarse en caché mediante la llamada setjmp (). Después de una llamada longjmp (), tales variables pueden tener el valor que tenían cuando se realizó setjmp (), o los valores que tenían cuando llamaban a la rutina que finalmente llamaba longjmp (), o cualquier combinación de los mismos. En al menos algunos dialectos en C, dichas variables pueden declararse "volátiles" para evitar que se almacenen en caché.
Aunque setjmp / longjmp () a veces puede ser útil, también pueden ser muy peligrosos. En la mayoría de los casos, no existe un código de protección que cause Comportamiento no definido, y en muchos escenarios del mundo real, el uso indebido puede ocasionar que sucedan cosas malas (a diferencia de algunos tipos de Comportamiento no definido, donde el resultado real a menudo puede coincidir con programador previsto).
Esto explica las cosas bastante bien.
setjmp()/longjmp()
no están destinados a guardar la pila, para eso son setcontext()/getcontext()
.
El estándar especifica que el valor de las variables automáticas no volátiles definidas en la función que llama a setjmp()
que se cambian entre las llamadas setjmp()
y longjmp()
no se especifican después de un longjmp()
. También existen algunas restricciones sobre cómo llamar a setjmp()
por la misma razón.