You don't need to pass it by address to a function. Something like this could trigger it:
// region 1:
// x is allocated to register r1
int x = 0; // store 0 to r1
jmpbuf jb;
if (setjmp(jb) == 0) {
// region 2:
// x is re-allocated to r2, so r1 is copied to r2
x = 42; // store 42 to r2
longjmp(jb, 1);
}
// region 3:
// x is allocated to r1
printf("%d\n", x); // load from r1
The compiler is allowed to allocate the same variable to different locations at different points in the program. At re-allocation boundaries, it will insert register moves or stack spills or whatever. The problem in the example is that the longjmp() crosses a re-allocation barrier, so the store to r2 won't be registered as a store to x in region 3.
Actually, this would still occur even if x was always allocated to r1. The reason is that before calling setjmp() the compiler spills x to the stack, so the callee can use the registers for its own use, and on returning (either via longjmp() or the first time through) it will restore x from the value on the stack.
I tried the example above with GCC. At the default optimization level it printed 42. But with -O2 it printed 0. Looking at the assembly code, it's actually even worse than I described. The compiler has treated the longjmp() akin to an exit() and so has determined that the x = 42 is actually a no-op that can be eliminated. This is similar to how in printf("1"); exit(0); printf("2"); the compiler will actually remove the second printf() as dead code from the executable.
So, in conclusion, there seems to be a whole bunch of ways in which the compiler could screw this up while staying within the bounds of standards-acceptable behavior.
Actually, this would still occur even if x was always allocated to r1. The reason is that before calling setjmp() the compiler spills x to the stack, so the callee can use the registers for its own use, and on returning (either via longjmp() or the first time through) it will restore x from the value on the stack.
I tried the example above with GCC. At the default optimization level it printed 42. But with -O2 it printed 0. Looking at the assembly code, it's actually even worse than I described. The compiler has treated the longjmp() akin to an exit() and so has determined that the x = 42 is actually a no-op that can be eliminated. This is similar to how in printf("1"); exit(0); printf("2"); the compiler will actually remove the second printf() as dead code from the executable.
So, in conclusion, there seems to be a whole bunch of ways in which the compiler could screw this up while staying within the bounds of standards-acceptable behavior.