'Using setjmp and longjmp with a local jmp_buf
In the case that a local jmp_buf
is actually represented by registers rather than stack memory, is it possible for setjmp
or longjmp
to cause the contents of the local jmp_buf
to be indeterminate when setjmp
returns from a longjmp
?
The suggested duplicate Is it allowed to do longjmp() multiple times for one setjmp() call? asks in the context of a global variable. It was suggested since the answer explains that the variable is not modified in a way that would prevent it from being subsequently called, that sufficiently answers the question for a local variable too.
However, treatment of a local variable differs from a global variable. In particular, if the local jmp_buf
variable is actually held in registers and not memory, restoration after longjmp
may not render a reusable jmp_buf
variable.
As an academic exercise, I was attempting to use setjmp
as a substitute for goto
. To keep the loop replacement local to the function, the jmp_buf
used is also a local variable.
void foo (int n) {
jmp_buf jb;
volatile int i;
i = setjmp(jb);
if (i < n) {
do_stuff(i);
longjmp(jb, ++i);
}
}
I understand that non-volatile local variables that have been modified between the setjmp
call and the longjmp
call are unspecified after longjmp
. However, I was curious about the local jmp_buf
variable itself, particularly in the case where the jmp_buf
variable is represented by registers rather than memory on the stack.
It is unclear if longjmp
itself can be considered something that may modify the local jmp_buf
variable, and whether this means its contents are unspecified when setjmp
returns after the call to longjmp
.
I thought I could easily dispatch the issue by declaring jb
to be volatile
, but this triggered a warning (which I treat as an error):
... error: passing argument 1 of ‘_setjmp’ discards ‘volatile’ qualifier from pointer target type [-Werror=discarded-qualifiers]
setjmp(jb);
^~
Also, the specification of setjmp
does not speak to whether it is saving the register values as they would be after setting the jmp_buf
or before setting the jmp_buf
.
If I need to be concerned about it, I can create a volatile copy of the jmp_buf
and copy its contents around. But, I'd like to avoid that if it isn't required.
Solution 1:[1]
TL;DR Since the standard isn't clear, it is better to treat the value of a local jmp_buf
as indeterminate after a local longjmp
.
ISO/IEC 9899:2018 §17.13.1.1 ¶2 describes the behavior of setjmp
, and ¶3 describes what happens on return.
The
setjmp
macro saves its calling environment in itsjmp_buf
argument for later use by thelongjmp
function....
If the return is from a direct invocation, the
setjmp
macro returns the value zero. If the return is from a call to thelongjmp
function, thesetjmp
macro returns a nonzero value.
We infer that a successful return from setjmp
results in an initialized jmp_buf
argument. However, there is no mention if the initialization takes into account of the jmp_buf
itself having automatic storage duration (and so, itself could be represented by registers rather than by memory).
ISO/IEC 9899:2018 §7.13.2.1 ¶3 describes the behavior of longjmp
, and is worded the same as the 2011 text cited by Marko:
All accessible objects have values, and all other components of the abstract machine254) 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 correspondingsetjmp
macro that do not have volatile-qualified type and have been changed between thesetjmp
invocation andlongjmp
call are indeterminate.
254)This includes, but is not limited to, the floating-point status flags and the state of open files.
However, the meaning of the word between is somewhat elusive. The standard could have explicitly specified the context of between to mean after setjmp
completed. For example, the wording could have stated:
... changed between the
setjmp
return andlongjmp
call are indeterminate.
The current wording suggests that one should include the invocation of setjmp
itself as something that may trigger the indeterminate condition.
There is a possibility that the semantics of the return of longjmp
covers for this problem, however. ISO/IEC 9899:2018 §17.13.2.1 ¶4 states:
After
longjmp
is completed, thread execution continues as if the corresponding invocation of thesetjmp
macro had just returned the value specified byval
. ...
This sentence could be interpreted to mean that the invocation semantics of setjmp
is the same whether it returns from direct invocation or returns from a longjmp
function. That is, the return of setjmp
means the jmp_buf
argument is initialized and can be used by another longjmp
. But again, this is not clear. In the most limiting interpretation, the as if clause only speaks to the value returned by setjmp
, and not the invocation itself.
Since the semantics are ambiguous, it is proper to treat the jmp_buf
object value as indeterminate upon return from longjmp
.
Solution 2:[2]
The C11 standard section §7.13.2.1 point 3 states:
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 correspondingsetjmp
macro that do not havevolatile
-qualified type and have been changed between thesetjmp
invocation andlongjmp
call are indeterminate.
Your jmp_buf
object is not changed between setjmp(jb)
and longjmp(jb, ++i)
. The only variable which is changed between the calls is i
, which is declared volatile
, as the standard suggests.
So, to answer your question, longjmp
cannot by itself "modify the contents of the local jmp_buf
[in such a way] that would cause its contents to be undefined when setjmp
returns", but modifying the jmp_buf
between the two calls through other means could definitely cause trouble.
Solution 3:[3]
That's fine.
On a related note, you don't need volatile
on i
because it's assigned to by setjmp()
.
On a very careful reading of the man page for longjmp()
and my copy of K&R C, the contents of jb
are only invalid within the body of your function, meaning if there were a second call to longjmp()
, it would see a valid view of jb
. Under the resaonable assumption that valid code does not become invalid in newer standard versions, this will still apply today.
TL;DR you don't need to mark variables of type jmp_buf
volatile.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | jxh |
Solution 2 | Marco Bonelli |
Solution 3 |