C 标准规定*任何类型(明确禁止的类型除外 - 见下文)都可以用
_Atomic
限定,例如:
struct S {
char a[4096];
};
_Atomic struct S s1, s2;
这是否意味着这样的分配
struct
:
s1 = s2; // atomic?
是原子的吗? 生成的 x86-64 程序集(例如)调用了
__atomic_load
和 __atomic_store
,这是否意味着复制整个 4K 数组是原子完成的,即,没有其他线程可以访问 s2
,而加载它并且在存储到其中时没有其他线程可以访问s1
?
如果它不是真正的原子性,为什么编译器至少不发出警告? 在这样的
_Atomic
上允许 struct
有什么用?
我的印象是你只能制作小尺寸的物体(
sizeof(T)
<= 16) be atomic.
* C11 标准,§6.7.2.4¶3:
原子类型说明符中的类型名称不得引用数组类型、函数类型、原子类型或限定类型。
同上。 §6.7.3¶3:
限定符修饰的类型不能是数组类型或函数类型。_Atomic
对我来说,这意味着任何未明确禁止的类型都是允许的。 FWIW,
gcc
和clang
都毫无怨言地编译了代码。
如果您对
struct
内的数组感到困扰,那么您可以按照Peter的建议进行操作,例如:
struct S2 {
int a1, a2, a3, a4, a5, a6, a7, a8, a9, a10;
int a11, a12, a13, a14, a15, a16, a17, a18, a19, a20;
// ...
};
为这样的
struct
分配两个变量的代码也将毫无抱怨地编译并生成涉及__atomic_load
和__atomic_store
的机器代码。
这是否意味着这样一个结构的赋值:
s1 = s2; // atomic?
是原子的吗?
这似乎是语言规范的意图,就其本身的“原子”意义而言,尽管它并没有完全按照这些术语进行阐述。 但是,您需要了解规范中“原子”的含义与 C 内存模型相关,而不一定与实现使用的 CPU 指令类型相关。
C 定义所有格式正确的
_Atomic
限定(或 C23 中的 atomic
限定)类型都是“原子类型”(C23 6.2.5/25),并且它指定了对象行为的各种细节具有原子类型,无需进一步区分。 特别是,它说
具有原子类型的对象的加载和存储是通过
语义完成的。memory_order_seq_cst
(C23 6.2.6.1/9)
我采用内存排序规范来假定原子性。
此外,虽然简单分配的规范没有具体说明此类分配是“原子地”执行的,但它们确实引用了 6.2.6.1/9(在 C23 6.5.17.1/2 中)。 此外,复合赋值操作的规范规定,当左操作数具有原子类型时
当[左操作数]具有原子类型时,复合赋值是具有
内存顺序语义的读取-修改-写入操作。memory_order_seq_cst
这不仅是对内存排序语义的另一个引用,而且“读取-修改-写入”是原子操作的术语,特别是在具有原子类型的左操作数的上下文中给出。
我的印象是你只能制作小尺寸的物体(sizeof(T)<= 16) be atomic.
语言规范没有这样的限制。 正如您所观察到的,它确实规定了数组类型和函数类型不得具有
_Atomic
限定,但除此之外,语言语法允许任何类型具有_Atomic
限定,定义了这样的含义类型,并指定与原子类型相关的行为,而不将此类类型划分为不同的类别(除非下面描述)。
我认为您的印象与对如何实现原子类型和操作的特定期望有关,但规范并未解决该级别。 但请注意,C 明确允许在锁的帮助下(透明地)完成原子操作。 为此,它允许原子类型的大小可以不同于其非原子类型的大小,并且它提供了函数
atomic_is_lock_free()
来确定特定原子类型是否是无锁的。 您应该期望实现将使用此功能来实现 C 原子操作语义,而它们不能或不会选择通过专用 CPU 指令或类似的低级机制来实现。