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.