I would like to see the GCC project to document that if the address of a
member is taken, this does not constitute an access to the object as a
whole.
That is, in the following code:
#include <stdatomic.h>
struct S {
_Atomic int a;
int b;
};
int
load_a (struct S *p)
{
return atomic_load_explicit (&p->a, memory_order_relaxed);
}
int
store_b (struct S *p, int b)
{
p->b = b;
}
If one thread calls load_a and another thread calls store_b on the same
struct S *, no data race happens.
This is an extension over the C standard because of the way “->” is
defined. C requires that E1->E2 it is evaluated as (*(E1))->E2, and *E1
is defined as an access to the entire struct, so there is a data race in
load_a with the assignment in store_b.
This is somewhat complicated to fix, wording-wise, because for the
purpose of aliasing analysis, we need the whole-struct access to *E1,
otherwise there is nothing that asserts the dynamic type of this memory
location. But without such an assertion, the alias analysis GCC
currently performs would be wrong.
A similar extension is needed for access to embedded synchronization
types (such as mutexes), but it is even harder to express properly.
Of course, there is a workaround to make this issue go away, like this:
#define member_type(type, member) __typeof__ (((type) {}).member)
#define member_address(this, member) \
(member_type (__typeof__ (*(this)), member) *) \
(((char *) (this) + offsetof (__typeof__ (*(this)), member)))
int
load_a_ (struct S *p)
{
return atomic_load_explicit (member_address (p, a),
memory_order_relaxed);
}
int
store_b_ (struct S *p, int b)
{
*member_address (p, b) = b;
}
But it seems to be silly to write code this way. (We actually use these
macros in libio, where we have to implement C++ class inheritance in C.)
For C++, we can probably fix this in the standard in some way, so that
no extension is required.
Thanks,
Florian