user1244932
user1244932

Reputation: 8112

call empty function with address of local variable before setjmp, what for?

I read code of C library and can not understand what is going on:

struct Foo *foo = NULL;
lib_var((void *)&foo);
if (setjmp(get_jmp_buf()) == 0) {
  foo = ...;
  // other calculation that may cause longjmp
} else {
  //something bad happens
  drop_if_not_null(foo);
}

where lib_var is function that has such prototype void lib_var(void *);, and such implementation in separate C file:

void lib_var(void *variable)
{
// nothing
}

So what for lib_var function, something like volatile to force compiler reread variable from memory in setjmp() != 0 case? Is this undefined behavior? Because of I am sure LTO remove such completely useless code, and LTO should not change behavior of confirming to standard code.

Upvotes: 0

Views: 71

Answers (1)

Nate Eldredge
Nate Eldredge

Reputation: 58393

Yes, it's undefined behavior from the standpoint of standard C - or at least, the value of foo is indeterminate and can't be safely used for anything.

C17 (n2176) 7.13.2.1 (3):

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.

That's precisely the situation here: foo has automatic storage duration and is local to the function where setjmp is invoked, it's not volatile-qualified, and it is changed between setjmp and longjmp.

The authors of the code were probably thinking that passing &foo to lib_var would ensure that it is allocated in memory, not in a register, and that since the compiler couldn't know whether lib_var would modify foo, it couldn't constant-propagate the value NULL into the else block. But as you say, LTO or various other optimizations could break this. It's possible the authors intended only to support a particular compiler that they knew didn't do any such thing, or that provided some stronger guarantees beyond what the standard says, but it's also possible they were just confused.

The correct fix would be to declare foo as volatile, i.e. struct Foo * volatile foo = NULL;.

Upvotes: 2

Related Questions