The C standard says* that any type (except those explicitly prohibited — see below) can be qualified with _Atomic
, e.g.:
struct S {
char a[4096];
};
_Atomic struct S s1, s2;
Does that mean that an assignment of such a struct
:
s1 = s2; // atomic?
is atomic? The generated x86-64 assembly (for example) has a call to __atomic_load
and __atomic_store
, so does that mean copying the entire 4K array is done atomically, i.e.., no other thread can be accessing s2
while loading it and no other thread can be accessing s1
while storing into it?
If it's not truly atomic, why doesn't the compiler at least warn? And what use is allowing _Atomic
on such a struct
?
I was under the impression that you could make only small-sized objects (sizeof(T)
<= 16) be atomic.
* C11 standard, §6.7.2.4¶3:
The type name in an atomic type specifier shall not refer to an array type, a function type, an atomic type, or a qualified type.
Ibid. §6.7.3¶3:
The type modified by the
_Atomic
qualifier shall not be an array type or a function type.
To me, that means that any type not explicitly forbidden is allowed. FWIW, both gcc
and clang
compile the code without complaint.
If you're bothered by the array inside the struct
, then you can do as Peter suggested, e.g.:
struct S2 {
int a1, a2, a3, a4, a5, a6, a7, a8, a9, a10;
int a11, a12, a13, a14, a15, a16, a17, a18, a19, a20;
// ...
};
Code assigning two variables of such a struct
will also compile without complaint and generate machine code involving __atomic_load
and __atomic_store
.
Does that mean that an assignment of such a struct:
s1 = s2; // atomic?
is atomic?
That very much seems to be the intent of the language spec, in it's own sense of "atomic", though it is not laid out in exactly those terms. You need to understand, however, that the spec's sense of "atomic" is associated with the C memory model, not necessarily with the kinds of CPU instructions an implementation uses.
C defines that all well-formed _Atomic
-qualified (or atomic
-qualified in C23) types are "atomic types" (C23 6.2.5/25), and it specifies all kinds of details of the behavior of objects with atomic type, without further discrimination. In particular, it says that
Loads and stores of objects with atomic types are done with
memory_order_seq_cst
semantics.
(C23 6.2.6.1/9)
I take the specification of a memory ordering to presume atomicity.
Additionally, although the specifications for simple assignment do not specifically say that such assignments are performed "atomically", they do reference 6.2.6.1/9 (in C23 6.5.17.1/2). Moreover, the specifications for compound assignment operations say that
when [the left operand] has an atomic type, compound assignment is a read-modify-write operation with
memory_order_seq_cst
memory order semantics.
(C23 6.5.17.3/4)
Not only is this another reference to memory-ordering semantics, but the "read-modify-write" is the terminology of atomic operations, given specifically in the context of a left operand having atomic type.
I was under the impression that you could make only small-sized objects (sizeof(T) <= 16) be atomic.
The language spec places no such restriction. As you observe, it does lay a constraint that array types and function types shall not be _Atomic
-qualified, but otherwise, the language syntax allows any object type to be _Atomic
-qualified, the spec defines what that means about such a type, and it specifies behaviors related to atomic types without dividing such types into different categories (except as described next).
I suppose that your impression was related to particular expectations about how atomic types and operations should be implemented, but the spec does not address that level. Note well, however, that C explicitly permits atomic operations to be accomplished with the assistance of a lock (transparently). For this purpose, it allows that the sizes of atomic types may be different from the sizes of their non-atomic counterparts, and it provides function atomic_is_lock_free()
(C23 7.17.5.2) for determining whether particular atomic types are lock-free. You should expect that implementations will use locks to implement C atomic operation semantics that they do not, for whatever reason, implement via for-purpose CPU instructions or similar low-level mechanisms.