Implementaciones C/C++ donde longjmp se desenrolla?
exception-handling stack-unwinding (2)
Intento entender los objetivos lógicos que se intentan lograr aquí.
La página del manual setjmp (3) establece:
setjmp () guarda el contexto / entorno de la pila en env para su posterior uso por longjmp (3). El contexto de la pila se invalidará si la función que llamó a setjmp () regresa.
Esto dice que si regresas del contexto de la pila donde se realizó la llamada a setjmp (), ya no podrás regresar a ella. De lo contrario, comportamiento indefinido.
Ok, entonces me parece que en el punto en que se realiza una llamada longjmp válida, setjmp debe estar en algún lugar en el contexto de la pila actual. Por lo tanto, un salto largo que desenrolla la pila y llama a todos los destructores de variables automáticas, etc., parece ser lógicamente equivalente a lanzar una excepción, y atraparla en el punto en que se realizó originalmente la llamada setjmp ().
¿De qué manera arrojar y atrapar una excepción es diferente de la semántica setjmp / longjmp deseada? Si, por ejemplo, tuviste la implementación deseada de setjmp / longjmp, entonces ¿cómo es diferente reemplazarla con un try / throw común y capturar la excepción lanzada?
Las únicas diferencias que pude ver es el alcance interno extra introducido por el bloque try / catch; mientras que setjmp realmente no abre un nuevo alcance interno.
Entonces, la respuesta aquí parece ser muy fácil: cada implementación compatible con C ++ tiene un equivalente setjmp / longjmp que tiene la semántica deseada. Se llama try / throw / catch.
¿Hay implementaciones importantes de C / C ++ donde la función longjmp
"se desenrolla", es decir, donde interactúa con destructores para objetos de almacenamiento automático, __attribute__((__cleanup__(...)))
, manipuladores de cancelación de subprocesos POSIX, etc. en lugar de solo restaurar el contexto de registro guardado por setjmp
? Estoy particularmente interesado en la existencia (o inexistencia) de implementaciones POSIX con esta propiedad, pero C / C ++ en general también son interesantes.
Para la recompensa, estoy buscando un sistema POSIX conformes o al menos un sistema POSIX, a diferencia de Windows que ya se ha mencionado.
Interix (SUA) de forma predeterminada no llama a destructores, pero en el modo x86, tiene una opción para ello.
Tomando este programa de prueba, guardado como test.cc
:
#include <stdio.h>
#include <setjmp.h>
struct A {
~A() { puts("~A"); }
};
jmp_buf buf;
void f() {
A a;
longjmp(buf, 1);
}
int main() {
if (setjmp (buf))
return 0;
f();
}
así es como se comporta Interix. Para abreviar, he omitido la configuración adecuada de PATH
.
$ cc -mx86 test.cc && ./a.out $ cc -mx86 -X /EHa test.cc && ./a.out cl : Command line warning D9025 : overriding ''/EHs'' with ''/EHa'' ~A $ cc -mamd64 test.cc && ./a.out $ cc -mamd64 -X /EHa test.cc && ./a.out cl : Command line warning D9025 : overriding ''/EHs'' with ''/EHa'' $
Los comentarios sugieren que cc -X /EHa
no se ajusta a POSIX, por ejemplo porque /EHa
captaría señales. Eso no es del todo cierto:
$ cat test.cc #include <signal.h> int main() { try { raise(SIGFPE); } catch (...) { // ignore } } $ cc -mx86 -X /EHa test.cc && ./a.out cl : Command line warning D9025 : overriding ''/EHs'' with ''/EHa'' Floating point exception (core dumped)
Si cambio raise(SIGFPE)
a una división por cero, sí veo que el manejador de excepciones lo detecta, pero ni POSIX ni C ++ requieren un comportamiento particular para eso, por lo que eso no afecta la conformidad. Tampoco es el caso que todas las señales asincrónicas estén atrapadas: para este programa:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
void sigint(int signal) {
puts("sigint");
exit(0);
}
int main() {
signal(SIGINT, sigint);
try {
for (;;) ;
} catch (...) {
// ignore
}
}
"sigint" se imprime después de Ctrl-C, como se esperaba. No veo una razón para afirmar que esta implementación no cumple con los requisitos de POSIX.