To reiterate, we have the following data definitions:
struct foo { int a; int b; }; struct foo static_foo = { 42, 17 }; struct foo *foo_p = &static_foo;
At some random point during runtime, the following code executes:
foo_p = NULL;
The question is whether readers can do the following:
p = foo_p; if (p != NULL) do_something_with(p->a, p->b);
And the answer is that this does not work. The reason is that the compiler is within its rights to transform this code as follows:
if (foo_p != NULL) do_something_with(foo_p->a, foo_p->b);
This might in fact be an entirely reasonable transformation in cases of
excessive register pressure.
Entirely reasonable, that is, if the code was single-threaded.
Unfortunately, this transformation can break multithreaded code.
For example, if foo_p
was set to NULL
just after the
if
condition was evaluated, but before the arguments to
do_something_with()
were evaluated, the argument evaluation
would segfault.
How to fix this? In the Linux kernel, you would use the
ACCESS_ONCE()
primitive as follows:
p = ACCESS_ONCE(foo_p); if (p != NULL) do_something_with(p->a, p->b);
ACCESS_ONCE()
is a volatile cast that prevents the compiler
from refetching foo_p
.
Similar features are provided by the upcoming
C
and
C++
standards.
These standards must also deal with more challenging situations, such as
making this sort of code work on an 8-bit CPU with 16-bit addressing,
so that the machine is incapable of fetching or storing a pointer
in a single access.
But that is a topic for another time.