After invoking longjmp(), non-volatile-qualified local objects should not be accessed if their values could have changed since the invocation of setjmp(). Their value in this case is considered indeterminate, and accessing them is undefined behavior.
Now my question is why volatile works in this situation? Wouldn't change in that volatile variable still fail the longjmp? For example, how longjmp will work correctly in the example given below? When the code get backs to setjmp after longjmp, wouldn't the value of local_var be 2 instead of 1?
void some_function()
{
volatile int local_var = 1;
setjmp( buf );
local_var = 2;
longjmp( buf, 1 );
}
setjmp and longjmp are a pair of C function facilitating cross-procedure transfer of control. Typically they are used to allow resumption of execution at a known good point after an error. Both take as first argument a buffer, which is used to hold the machine state at the jump destination.
The volatile keyword is intended to prevent the compiler from applying any optimizations on objects that can change in ways that cannot be determined by the compiler. Objects declared as volatile are omitted from optimization because their values can be changed by code outside the scope of current code at any time.
This is mainly used to implement exception handling in C. setjmp can be used like try (in languages like C++ and Java). The call to longjmp can be used like throw (Note that longjmp() transfers control to the point set by setjmp()).
These are used in C for exception handling. The setjump() can be used as try block, and longjump() can be used as throw statement. The longjump() transfers control the pointe which is pointed by setjump(). Here we will see how to print a number 100 times without using recursion, loop, or macro expansion.
setjmp
and longjmp
clobber registers. If a variable is stored in a register, its value gets lost after a longjmp
.
Conversely, if it's declared as volatile
, then every time it gets written to, it gets stored back to memory, and every time it gets read from, it gets read back from memory every time. This hurts performance, because the compiler has to do more memory accesses instead of using a register, but it makes the variable's usage safe in the face of longjmp
ing.
The crux is in the optimization in this scenario: The optimizer would naturally expect that a call to a function like setjmp() does not change any local variables, and optimize away read accesses to the variable. Example:
int foo;
foo = 5;
if ( setjmp(buf) != 2 ) {
if ( foo != 5 ) { optimize_me(); longjmp(buf, 2); }
foo = 6;
longjmp( buf, 1 );
return 1;
}
return 0;
An optimizer can optimize away the optimize_me line because foo has been written in line 2, does not need to be read in line 4 and can be assumed to be 5. Additionally, the assignment in line 5 can be removed because foo would be never read again if longjmp was a normal C functon. However, setjmp() and longjmp() disturb the code flow in a way the optimizer cannot account for, breaking this scheme. The correct result of this code would be a termination; with the line optimized away, we have an endless loop.
The most common reason for problems in the absence of a 'volatile' qualifier is that compilers will often place local variables into registers. These registers will almost certainly be used for other things between the setjmp and longjmp. The most practical way to ensure that the use of these registers for other purposes won't cause the variables to hold the wrong values after the longjmp is to cache the values of those registers in the jmp_buf. This works, but has the side effect that there is no way for the compiler to update the contents of the jmp_buf to reflect changes made to the variables after the registers are cached.
If that were the only problem, the result of accessing local variables not declared volatile would be indeterminate, but not Undefined Behavior. There's a problem even with memory variables, though, which thiton alludes to: even if a local variable happens to be allocated on the stack, a compiler would be free to overwrite that variable with something else any time it determines that its value is no longer needed. For example, a compiler could identify that some variables are never 'live' when a routine calls other routines, place those variables shallowest in its stack frame, and pop them before calling other routines. In such a scenario, even though the variables existed in memory when setjmp() is called, that memory might have been reused for something else like holding return address. As such, after the longjmp() is performed, the memory would be considered uninitialized.
Adding a 'volatile' qualifier to a variable's definition causes storage to be reserved exclusively for the use of that variable, for as long as it is within scope. No matter what happens between the setjmp and longjmp, provided control has not left the scope where the variable was declared, nothing is allowed to use that location for any other purpose.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With