Why volatile works for setjmp/longjmp

2020-02-03 06:38发布

问题:

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 );
}

回答1:

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 longjmping.



回答2:

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.



回答3:

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.