Реализации C/С++, где longjmp отключается?
Существуют ли основные реализации C/С++, где функция longjmp
"разматывается", то есть где она взаимодействует с деструкторами для объектов автоматического хранения, __attribute__((__cleanup__(...)))
, обработчики отмены потоков POSIX и т.д., а не просто восстановление контекста регистра сохранено setjmp
? Мне особенно интересно существование (или небытие) реализаций POSIX с этим свойством, но C/С++ в целом также интересны.
Для щедрости я ищу POSIX-совместимую или, по крайней мере, POSIX-подобную систему, в отличие от Windows, о которой уже упоминалось.
Ответы
Ответ 1
Interix (SUA) по умолчанию не вызывает деструкторы, но в режиме x86 у него есть опция для него.
Взяв эту тестовую программу, сохраните ее как 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();
}
здесь, как ведет себя Interix. Для краткости я пропустил нужную настройку 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'
$
Комментарии предполагают, что cc -X /EHa
не соответствует POSIX, например, потому что /EHa
будет ловить сигналы. Это не совсем так:
$ 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)
Если я изменяю raise(SIGFPE)
на деление на ноль, я действительно вижу, что обработчик исключений его ловит, но ни POSIX, ни С++ не требуют какого-либо конкретного поведения для этого, поэтому это не влияет на соответствие. Также не так, что все асинхронные сигналы пойманы: для этой программы:
#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
}
}
"Сигинт" печатается после Ctrl-C, как и ожидалось. Я не вижу причин утверждать, что эта реализация не отвечает требованиям POSIX.
Ответ 2
Я пытаюсь понять логические цели, которые пытаются выполнить здесь.
На странице справочной страницы setjmp (3) указано:
setjmp() сохраняет контекст/среду стека в env для последующего использования посредством longjmp (3). Контекст стека будет недействительным, если функция который вызвал setjmp().
Это говорит о том, что если вы вернетесь из контекста стека, где был вызван вызов setjmp(), вы больше не можете возвращать ему longjmp. В противном случае поведение undefined.
Итак, мне кажется, что в точке, где выполняется действительный вызов longjmp, setjmp должен быть где-то в текущем контексте стека. Таким образом, longjmp, который разматывает стек и вызывает все деструкторы автоматических переменных и т.д., По-видимому, логически эквивалентен бросанию исключения и вылавливает его в тот момент, когда изначально был сделан вызов setjmp().
Как бросать и ловить исключение отличается от вашей желаемой семантики setjmp/longjmp? Если, скажем, у вас была желаемая реализация setjmp/longjmp, то как заменить ее обычным try/throw и поймать заброшенное исключение, будет отличаться?
Единственные различия, которые я мог видеть, - это дополнительная внутренняя область, введенная блоком try/catch; в то время как setjmp действительно не открывает новую внутреннюю область.
Итак, ответ здесь очень прост: каждая совместимая реализация С++ имеет эквивалент setjmp/longjmp, который имеет желаемую семантику. Это называется try/throw/catch.