将指向原子类型的指针分配给指向非原子类型的指针
Assigning pointers to atomic type to pointers to non atomic type
这段代码的行为是否定义明确?
#include <stdatomic.h>
const int test = 42;
const int * _Atomic atomic_int_ptr;
atomic_init(&atomic_int_ptr, &test);
const int ** int_ptr_ptr = &atomic_int_ptr;
printf("int = %d\n", **int_ptr_ptr); //prints int = 42
我将一个指向原子类型的指针分配给了一个指向非原子类型的指针(类型相同)。以下是我对这个例子的看法:
标准明确规定了 const
、volatile
和 restrict
限定符与 _Atomic
限定符 6.2.5(p27)
:
的区别
this Standard explicitly uses the phrase ‘‘atomic, qualified or
unqualified type’’ whenever the atomic version of a type is permitted
along with the other qualified versions of a type. The phrase
‘‘qualified or unqualified type’’, without specific mention of atomic,
does not include the atomic types.
合格类型的兼容性也定义为6.7.3(p10)
:
For two qualified types to be compatible, both shall have the
identically qualified versionof a compatible type; the order of
type qualifiers within a list of specifiers or qualifiers does
not affect the specified type.
结合上面引用的引号,我得出结论,原子类型和非原子类型是兼容类型。因此,应用简单分配规则 6.5.16.1(p1)
(emp.mine):
the left operand has atomic, qualified, or unqualified pointer
type, and (considering the type the left operand would have
after lvalue conversion) both operands are pointers to qualified
or unqualified versions of compatible types, and the type pointed to by
the left has all the qualifiers of the type pointed to by the right;
所以我得出结论,该行为定义明确(即使将原子类型分配给非原子类型)。
所有这些的问题在于应用上述规则我们还可以得出结论将非原子类型简单赋值给原子类型 也有明确的定义,这显然不是真的,因为我们有一个专用的通用 atomic_store
函数。
C11 允许 _Atomic T
具有与 T
不同的大小和布局,例如如果它不是无锁的。 (参见@PSkocik 的回答)。
例如,实现可以选择在每个原子对象中放置一个互斥锁,并将其放在最前面。 (大多数实现使用地址作为 table 锁的索引: 而不是膨胀不保证锁定的 _Atomic
或 std::atomic<T>
对象的每个实例-编译时免费)。
因此_Atomic T*
即使在单线程程序中也不兼容T*
。
仅仅分配一个指针可能不是 UB(抱歉我没有戴上语言律师的帽子),但解引用肯定可以.
我不确定在 _Atomic T
和 T
确实共享相同布局和对齐方式的实现中它是否是严格的 UB。如果 _Atomic T
和 T
被认为是不同的类型,无论它们是否共享相同的布局,它可能违反了严格的别名。
alignof(T)
可能与 alignof(_Atomic T)
不同,但除了故意的不正当实施(Deathstation 9000),_Atomic T
将位于至少与普通 T
一样对齐,因此将指针转换为已存在的对象不是问题。比需要更对齐的对象不是问题,如果它阻止编译器使用单个更宽的负载,则可能是优化失误。
有趣的事实:即使没有解除引用,创建欠对齐指针在 ISO C 中也是 UB。 (大多数实现不会抱怨,英特尔的 _mm_loadu_si128
内在甚至需要编译器支持这样做。)
在实际实现的实践中,_Atomic T*
和 T*
使用相同的布局/对象表示和 alignof(_Atomic T) >= alignof(T)
。如果您可以解决严格别名 UB,则程序的单线程或互斥保护部分可以对 _Atomic
对象进行非原子访问。也许 memcpy
.
在实际实现中,_Atomic
可能会增加对齐要求,例如大多数 64 位 ISA 的大多数 ABI 上的 struct {int a,b;}
通常只有 4 字节对齐(成员的最大值),但是 _Atomic
会给它自然对齐 = 8 以允许 loading/storing它带有一个对齐的 64 位 load/store。这当然不会改变成员相对于对象开头的布局或对齐方式,只会改变整个对象的对齐方式。
The problem with all that is that applying the rules above we can also conclude that simple assignment a non-atomic type to an atomic type is also well defined which is obviously not true since we have a dedicated generic atomic_store function for that.
不,这个推理是有缺陷的。
atomic_store(&my_atomic, 1)
等价于 my_atomic=1;
。在 C 抽象机中,它们都使用 memory_order_seq_cst
.
进行原子存储
您还可以通过查看任何 ISA 上真实编译器的代码生成来了解这一点;例如x86 编译器将使用 xchg
指令,或 mov
+mfence
。同样,shared_var++
编译为原子 RMW(mo_seq_cst
)。
不知道为什么有一个 atomic_store
泛型函数。也许只是为了与 atomic_store_explicit
形成对比/保持一致,它允许您执行 atomic_store_explicit(&shared_var, 1, memory_order_release)
或 memory_order_relaxed
来执行发布或放松存储而不是顺序发布。 (在 x86 上,只是一个普通的存储。或者在弱排序的 ISA 上,一些栅栏但不是完整的屏障。)
对于无锁类型,_Atomic T
和T
的对象表示是相同的,通过非原子指针访问原子对象在实践中没有问题在单线程程序中。不过,我怀疑它仍然是 UB。
C++20 计划引入 std::atomic_ref<T>
,它可以让你对非原子变量进行原子操作。 (没有 UB,只要在 window 写入期间没有线程可能对其进行非原子访问。)这基本上是 GCC 中 __atomic_*
内置函数的包装器,例如, std::atomic<T>
是在
之上实现的
(这会带来一些问题,比如如果 atomic<T>
比 T
需要更多对齐,例如对于 i386 System V 上的 long long
或 double
。或者一个结构2x int
在大多数 64 位 ISA 上。当声明你希望能够对其执行原子操作的非原子对象时,你应该使用 alignas(_Atomic T) T foo
。)
无论如何,我不知道在 portable ISO C11 中有任何符合标准的方法来做类似的事情,但是 它是值得的提到真正的 C 编译器非常支持对没有 _Atomic
. 声明的对象执行原子操作但是 only using stuff like GNU C atomic builtins.:
参见 :显然即使在 GNU C 中也不推荐将 T*
转换为 _Atomic T*
。尽管我们没有明确的答案它实际上是 UB。
Further, there is the _Atomic qualifier. The presence of the _Atomic
qualifier designates an atomic type. The size, representation, and
alignment of an atomic type need not be the same as those of the
corresponding unqualified type. Therefore, this Standard explicitly
uses the phrase ''atomic, qualified or unqualified type'' whenever the
atomic version of a type is permitted along with the other qualified
versions of a type. The phrase ''qualified or unqualified type'',
without specific mention of atomic, does not include the atomic types.
我认为这应该表明原子限定类型不被认为与它们所基于的类型的限定或非限定版本兼容。
这段代码的行为是否定义明确?
#include <stdatomic.h>
const int test = 42;
const int * _Atomic atomic_int_ptr;
atomic_init(&atomic_int_ptr, &test);
const int ** int_ptr_ptr = &atomic_int_ptr;
printf("int = %d\n", **int_ptr_ptr); //prints int = 42
我将一个指向原子类型的指针分配给了一个指向非原子类型的指针(类型相同)。以下是我对这个例子的看法:
标准明确规定了 const
、volatile
和 restrict
限定符与 _Atomic
限定符 6.2.5(p27)
:
this Standard explicitly uses the phrase ‘‘atomic, qualified or unqualified type’’ whenever the atomic version of a type is permitted along with the other qualified versions of a type. The phrase ‘‘qualified or unqualified type’’, without specific mention of atomic, does not include the atomic types.
合格类型的兼容性也定义为6.7.3(p10)
:
For two qualified types to be compatible, both shall have the identically qualified versionof a compatible type; the order of type qualifiers within a list of specifiers or qualifiers does not affect the specified type.
结合上面引用的引号,我得出结论,原子类型和非原子类型是兼容类型。因此,应用简单分配规则 6.5.16.1(p1)
(emp.mine):
the left operand has atomic, qualified, or unqualified pointer type, and (considering the type the left operand would have after lvalue conversion) both operands are pointers to qualified or unqualified versions of compatible types, and the type pointed to by the left has all the qualifiers of the type pointed to by the right;
所以我得出结论,该行为定义明确(即使将原子类型分配给非原子类型)。
所有这些的问题在于应用上述规则我们还可以得出结论将非原子类型简单赋值给原子类型 也有明确的定义,这显然不是真的,因为我们有一个专用的通用 atomic_store
函数。
C11 允许 _Atomic T
具有与 T
不同的大小和布局,例如如果它不是无锁的。 (参见@PSkocik 的回答)。
例如,实现可以选择在每个原子对象中放置一个互斥锁,并将其放在最前面。 (大多数实现使用地址作为 table 锁的索引:_Atomic
或 std::atomic<T>
对象的每个实例-编译时免费)。
因此_Atomic T*
即使在单线程程序中也不兼容T*
。
仅仅分配一个指针可能不是 UB(抱歉我没有戴上语言律师的帽子),但解引用肯定可以.
我不确定在 _Atomic T
和 T
确实共享相同布局和对齐方式的实现中它是否是严格的 UB。如果 _Atomic T
和 T
被认为是不同的类型,无论它们是否共享相同的布局,它可能违反了严格的别名。
alignof(T)
可能与 alignof(_Atomic T)
不同,但除了故意的不正当实施(Deathstation 9000),_Atomic T
将位于至少与普通 T
一样对齐,因此将指针转换为已存在的对象不是问题。比需要更对齐的对象不是问题,如果它阻止编译器使用单个更宽的负载,则可能是优化失误。
有趣的事实:即使没有解除引用,创建欠对齐指针在 ISO C 中也是 UB。 (大多数实现不会抱怨,英特尔的 _mm_loadu_si128
内在甚至需要编译器支持这样做。)
在实际实现的实践中,_Atomic T*
和 T*
使用相同的布局/对象表示和 alignof(_Atomic T) >= alignof(T)
。如果您可以解决严格别名 UB,则程序的单线程或互斥保护部分可以对 _Atomic
对象进行非原子访问。也许 memcpy
.
在实际实现中,_Atomic
可能会增加对齐要求,例如大多数 64 位 ISA 的大多数 ABI 上的 struct {int a,b;}
通常只有 4 字节对齐(成员的最大值),但是 _Atomic
会给它自然对齐 = 8 以允许 loading/storing它带有一个对齐的 64 位 load/store。这当然不会改变成员相对于对象开头的布局或对齐方式,只会改变整个对象的对齐方式。
The problem with all that is that applying the rules above we can also conclude that simple assignment a non-atomic type to an atomic type is also well defined which is obviously not true since we have a dedicated generic atomic_store function for that.
不,这个推理是有缺陷的。
atomic_store(&my_atomic, 1)
等价于 my_atomic=1;
。在 C 抽象机中,它们都使用 memory_order_seq_cst
.
您还可以通过查看任何 ISA 上真实编译器的代码生成来了解这一点;例如x86 编译器将使用 xchg
指令,或 mov
+mfence
。同样,shared_var++
编译为原子 RMW(mo_seq_cst
)。
不知道为什么有一个 atomic_store
泛型函数。也许只是为了与 atomic_store_explicit
形成对比/保持一致,它允许您执行 atomic_store_explicit(&shared_var, 1, memory_order_release)
或 memory_order_relaxed
来执行发布或放松存储而不是顺序发布。 (在 x86 上,只是一个普通的存储。或者在弱排序的 ISA 上,一些栅栏但不是完整的屏障。)
对于无锁类型,_Atomic T
和T
的对象表示是相同的,通过非原子指针访问原子对象在实践中没有问题在单线程程序中。不过,我怀疑它仍然是 UB。
C++20 计划引入 std::atomic_ref<T>
,它可以让你对非原子变量进行原子操作。 (没有 UB,只要在 window 写入期间没有线程可能对其进行非原子访问。)这基本上是 GCC 中 __atomic_*
内置函数的包装器,例如, std::atomic<T>
是在
(这会带来一些问题,比如如果 atomic<T>
比 T
需要更多对齐,例如对于 i386 System V 上的 long long
或 double
。或者一个结构2x int
在大多数 64 位 ISA 上。当声明你希望能够对其执行原子操作的非原子对象时,你应该使用 alignas(_Atomic T) T foo
。)
无论如何,我不知道在 portable ISO C11 中有任何符合标准的方法来做类似的事情,但是 它是值得的提到真正的 C 编译器非常支持对没有 _Atomic
. 声明的对象执行原子操作但是 only using stuff like GNU C atomic builtins.:
参见 T*
转换为 _Atomic T*
。尽管我们没有明确的答案它实际上是 UB。
Further, there is the _Atomic qualifier. The presence of the _Atomic qualifier designates an atomic type. The size, representation, and alignment of an atomic type need not be the same as those of the corresponding unqualified type. Therefore, this Standard explicitly uses the phrase ''atomic, qualified or unqualified type'' whenever the atomic version of a type is permitted along with the other qualified versions of a type. The phrase ''qualified or unqualified type'', without specific mention of atomic, does not include the atomic types.
我认为这应该表明原子限定类型不被认为与它们所基于的类型的限定或非限定版本兼容。