外部基础类型未按请求对齐时的atomic_ref
atomic_ref when external underlying type is not aligned as requested
我在p0019r8上读到以下内容:
atomic_ref(T& obj);
需要:被引用对象应与
required_alignment
对齐。
cppreference在未对齐时将其解释为UB:
如果obj未与required_alignment对齐,则行为未定义。
那么,您希望实现如何处理它呢
实现可以检查编译时alignof
,但实际上一个类型可能比alignof
对齐得更多。实现可以解释指针位并检查运行时对齐,但它是额外的运行时检查。
最终,我看到了以下选项:
- 什么都不做-以令人不快的方式实现运行时未定义的行为,只支持正确的使用
- 检查编译时对齐(
alignof
),如果错误则发出警告 -
检查编译时对齐(不正确,则在编译时失败,因为实际对齐可能大于静态类型可见的对齐alignof
),如果错误 -
检查编译时对齐(不正确,则在运行时失败,因为实际对齐可能大于静态类型可见的对齐alignof
),如果错误 - 检查编译时对齐(
alignof
),如果错误则回退到基于锁定 - 检查运行时对齐(指针位),如果错误则在运行时失败
- 检查运行时对齐(指针位),如果错误则回退到基于锁定
TL:DR:永远不要默默地回到锁定状态,没有人想要这样,因为它违背了std::atomic
的主要目的将非锁定视为可移植性的后备方案,而不是可行的操作模式
成为UB使得编译器在不进行任何检查的情况下简单地假设它是对齐的是合法的。能够在没有任何运行时检查的情况下进行假设是UB概念的主要好处之一。这是大多数人在优化构建的运行时所希望/期望的,而不是用可能会退回到使用互斥的条件分支来膨胀代码。
是否(以及如何)在此处定义任何行为完全取决于实现,这取决于实现质量以及性能与调试之间的权衡。我想你知道这一点,并且确实在问用户希望编译器为这些QoI选择选择什么,这很好。
正如你链接的P0019提案所说,这一切都归结为一个QOI问题:
- 引用能力约束
原子引用引用的对象必须满足可能特定于体系结构的约束。例如,对象可能需要在内存中正确对齐,或者可能不允许驻留在GPU寄存器内存中。我们不会列举所有潜在的约束,也不会指定违反这些约束时的行为当违反约束时,生成适当的信息是一个实现质量问题
"生成适当的信息"的措辞意味着,如果检测到违规,他们希望实现发出警告/出错,而不是返回锁定。
尽管可以回退到锁定的实现可能会愚蠢地将required_alignment
设置为正确性(1)的最小值,而不是锁定自由度的最小值。当然,没有人想要这样,但这是一个QoI问题,而不是标准合规性。
我希望(或至少hope)实现工作如下:
-
如果在
alignof
小于required_alignment
的任何对象上使用atomic_ref
,则在编译时发出警告。您可能知道某个T *p
恰好是8字节对齐的,即使alignof(T)
只有1或4,所以这不应该是错误。一些当地的方式来平息警告将是一件好事。(替代方案:承诺与GNU C
x = __builtin_assume_aligned(x, 16)
之类的编译器对齐)如果一个对象在编译时明确已知对齐不足,至少要发出警告,例如,对齐已知的结构的子成员,或者声明可见但不包括
alignas
的全局var。通过可能对齐不足的指针进行访问的警告噪音较大,应单独禁用。 -
超慢调试模式:运行时检查对齐,对原子性对齐不足的特定对象发出警告或中止。(例如
gcc -fsanitize=undefined
,或者MSVC的调试模式,它已经添加了std::vector::operator[]
边界检查之类的东西。我认为GCC的UBSan比MSVC调试模式做更多的检查,例如有符号溢出;我认为MSVC调试方式介于gcc -O0
和gcc -O0 -fsanitize=undefined
之间。) -
"释放"模式:零检查,只发出asm,其正确性取决于要对齐的对象。(还有gcc
-O0
,不带UBSan,它允许一致的调试,但不添加额外的检查。) -
没有人希望在编译时或运行时静默回退到互斥对象。这种操作模式基本上只是存在的,所以ISO C++可以要求在任何地方都支持该功能,而不会使其无法在某些目标上实现。
与对为其设计的数据结构同时执行几个相关原子操作的关键部分进行手动细粒度锁定相比,锁定的回退通常是非常次优的。人们使用
atomic<T>
(以及即将推出的atomic_ref<T>
)来提高性能,而大部分性能都被锁定破坏了。尤其是读取端的可扩展性。
脚注1:IIRC,alignof()
仅为类型而非对象指定,但在GNU C++中,它也适用于对象。我用这个作为编译器内部知识的简写,即某个对象使用alignas()
来过度对齐它
- 对RValue对象调用的LValue ref限定成员函数
- 将Ref对象作为类成员
- 如何从 std::atomic 中提取指针 T<T>?
- std::atomic和std::condition_variable wait,notify_*方法之间的区别
- 为什么我的 std::ref 无法按预期工作?
- std::memory_order for std::atomic:<T>:wait
- MESI协议和std::atomic-它是否确保所有写入立即对其他线程可见?
- 如何将 Eigen::Ref 与 pybind11 一起使用?
- 如何@ref同一方法的不同变体?
- 使用带有 ref 参数的成员函数创建线程时出现编译错误
- 在 lambda 表达式中使用 std::atomic
- C++std::atomic在程序员级别保证了什么
- 如果在 2 个线程中使用,是否值得将size_t声明为 std::atomic?
- std::bind 是否实现了 std::ref 和 std::cref 来消除函数调用的歧义?
- MyType 允许 std::atomic 的确切要求是什么<MyType>?
- 无法将类型"T&"的非常量左值引用绑定到类型"T"的右值 t++ std::atomic<T>
- 为什么要用 ref-qualifier 和
- gcc Atomic在gcc 4.1.1中内置了奇怪的行为
- 在 C++20 之前和之后初始化 std::atomic
- Red Hat:使用<atomic>编译很好,但链接器找不到__atomic_store_16;什么库?