Реализации 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.