简体   繁体   中英

C/C++ implementations where longjmp unwinds?

Are there major C/C++ implementations where the longjmp function "unwinds", ie where it interacts with destructors for automatic-storage objects, __attribute__((__cleanup__(...))) , POSIX threads cancellation handlers, etc. rather than just restoring the register context saved by setjmp ? I'm particularly interested in the existence (or non-existence) of POSIX implementations with this property, but C/C++ in general are also interesting.

For the bounty, I'm looking for a POSIX-conforming or at least POSIX-like system, as opposed to Windows which has already been mentioned.

I'm trying to understand the logical goals that are trying to be accomplished here.

The setjmp(3) manual page states:

setjmp() saves the stack context/environment in env for later use by longjmp(3). The stack context will be invalidated if the function which called setjmp() returns.

This says that if you return from the stack context where the setjmp() call was made, you can no longer longjmp back to it. Otherwise, undefined behavior.

Ok, So it seems to me that at the point that a valid longjmp call is made, setjmp must be somewhere in the current stack context. Therefore, a longjmp that unwinds the stack and calls all destructors of auto variables, etc, appears to be logically equivalent to throwing an exception, and catching it at the point the setjmp() call was originally made.

How does throwing and catching an exception is different from your desired setjmp/longjmp semantics? If, say, you had your wished-for setjmp/longjmp implementation, then how does replacing it with an ordinary try/throw, and catching the thrown exception, be different?

The only differences that I could see is the extra inner scope introduced by the try/catch block; while setjmp does not really open a new inner scope.

So, the answer here appears to be very easy: every compliant C++ implementation has a setjmp/longjmp equivalent that has the desired semantics. It's called try/throw/catch.

Interix (SUA) by default does not call destructors, but in x86 mode, does have an option for it.

Taking this test program, saved as 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();
}

here's how Interix behaves. For brevity, I've omitted the required proper setup of 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'
$

The comments suggest that cc -X /EHa does not conform to POSIX, for example because /EHa would catch signals. That is not entirely true:

$ 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)

If I change raise(SIGFPE) to a division by zero, I do indeed see that the exception handler catches it, but neither POSIX nor C++ requires any particular behaviour for that, so that doesn't affect conformance. It is also not the case that all asynchronous signals are caught: for this program:

#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" is printed after Ctrl-C, as expected. I don't see a reason for claiming that this implementation fails to meet POSIX requirements.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM