Home > OS >  Special treatment of setjmp/longjmp by compilers
Special treatment of setjmp/longjmp by compilers

Time:06-22

In Why volatile works for setjmp/longjmp, user greggo comments:

Actually modern C compilers do need to know that setjmp is a special case, since there are, in general, optimizations where the change of flow caused by setjmp could badly corrupt things, and these need to be avoided. Back in K&R days, setjmp did not need special handling, and didn't get any, and so the caveat about locals applied. Since that caveat is already there and (should be!) understood - and of course, setjmp use is pretty rare - there is no incentive for modern compilers to go to any extra lengths to fix the 'clobber' issue -- it would still be in the language.

Are there any references that elaborate on this and if this is true, can there safely exist (with behavior no more error-prone than that of standard setjmp/longjmp) custom-made implementations of setjmp/longjmp (e.g., maybe I'd like to save some extra (thread-local) context) that are named something different? Like is there anyway to tell compilers "this function is effectively setjmp/longjmp"?

CodePudding user response:

setjmp / longjmp guarantee no more and no less than what any opaque function (not inlinable) would look like for the optimizer. Side effects on non-volatile locals might have been re-ordered at compile time wrt. setjmp (or longjmp) if escape analysis can show that no global variable could have their address.

So I don't think they need any special attributes in their prototypes. glibc declares longjmp as __attribute__((noreturn)) which should make side-effects on locals happen before a call to it, and for example avoids warnings about execution falling off the end of a non-void function in something like int foo(){ if(x) return y; longjmp(jmpbuf); }

From glibc's /usr/include/setjmp.h

// earlier CPP macros to define __THROWNL as __attribute__ ((__nothrow__)) in C   mode

extern int setjmp (jmp_buf __env) __THROWNL;
extern void longjmp (struct __jmp_buf_tag __env[1], int __val)
     __THROWNL __attribute__ ((__noreturn__));
extern void siglongjmp (sigjmp_buf __env, int __val)
     __THROWNL __attribute__ ((__noreturn__));

There's a bunch of C preprocessor to define _ versions and so on, but overall it doesn't look like any of them need any special support from the compiler proper. (I don't think it relied on GCC / clang already recognizing those names as special builtin functions; if they did that, they'd define them as __builtin_setjmp if such a thing existed, but it doesn't in the GCC manual.)

CodePudding user response:

First of all, the correct answer to why volatile works in the linked posts is "because the C standard explicitly says so." I don't think the quoted part is true, because C explicitly lists a lot of poorly-defined behavior associated with setjmp/longjmp. The relevant part can be found in C17 7.13.2.1:

All accessible objects have values, and all other components of the abstract machine have state, as of the time the longjmp function was called, except that the values of objects of automatic storage duration that are local to the function containing the invocation of the corresponding setjmp macro that do not have volatile-qualified type and have been changed between the setjmp invocation and longjmp call are indeterminate.

Even C90 says more or less the same as the above. So the reason why compilers, modern or not, don't need to "fix this" is because C has never required them to. In the example where the quoted comment was posted, the second time that if ( foo != 5 ) is executed, the value of foo is indeterminate (and foo never has it's address taken), so strictly speaking that line simply invokes undefined behavior and the compiler can do as it pleases from there - it's a bug created by the application programmer, not the optimizer.

Generally, any application programmer using setjmp.h will get what is coming to them. It is the worst possible form of spaghetti programming.

  • Related