Casting pointers to _Atomic pointers and _Atomic s

2019-06-23 09:46发布

问题:

By my reading of the standard, *(_Atomic TYPE*)&(TYPE){0} (in words, casting a pointer to a non-atomic to a pointer to a corresponding atomic and dereferencing) isn't supported.

Do gcc and/or clang recognize it as an extension if TYPE is/isn't lock-free? (Question 1)

Second and related question: I was under the impression that if TYPE couldn't be implemented as a lock free atomic, a lock would need to be embedded in the corresponding _Atomic TYPE. But if I make TYPE a largish struct, then on both clang and gcc it has the same size as _Atomic TYPE.

Code for both problems:

#include <stdatomic.h>
#include <stdio.h>

#if STRUCT
typedef struct {
    int x;
    char bytes[50];
} TYPE;
#else
typedef int TYPE;
#endif

TYPE x;

void f (_Atomic TYPE *X)
{
    *X = (TYPE){0};
}

void use_f()
{
    f((_Atomic TYPE*)(&x));
}

#include <stdio.h>
int main()
{
    printf("%zu %zu\n", sizeof(TYPE), sizeof(_Atomic TYPE));
}

Now, if I compile the above snippet with -DSTRUCT, both gcc and clang keep the both the struct and its atomic variant at the same size, and they generate a call to a function named __atomic_store for the store (resolved by linking with -latomic).

How does this work if no lock is embedded in the _Atomic version of the struct? (Question 2)

回答1:

_Atomic changes alignment in some corner cases on Clang, and GCC will likely be fixed in the future as well (PR 65146). In these cases, adding _Atomic through a cast does not work (which is fine from a C standard point of view because it is undefined behavior, as you pointed out).

If the alignment is correct, it is more appropriate to use the __atomic builtins, which have been designed for exactly this use case:

  • Built-in Functions for Memory Model Aware Atomic Operations

As described above, this will not work in cases where the ABI provides insufficient alignment for plain (non-atomic) types, and where _Atomic would change alignment (with Clang only for now).

These builtins also work in case of non-atomic types because they use out-of-line locks. This is also the reason why no additional storage is required for _Atomic types, which use the same mechanism. This means that there is some unnecessary contention due to unintentional sharing of the locks. How these locks are implemented is an implementation detail which could change in future versions of libatomic.

In general, for types with atomic builtins that involve locking, using them with shared or aliased memory mappings does not work. These builtins are not async-signal-safe, either. (All these features are technically outside the C standard anyway.)