外部基础类型未按请求对齐时的atomic_ref

atomic_ref when external underlying type is not aligned as requested

本文关键字:atomic ref 对齐 请求 类型 外部      更新时间:2023-10-16

我在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问题:

  1. 引用能力约束

原子引用引用的对象必须满足可能特定于体系结构的约束。例如,对象可能需要在内存中正确对齐,或者可能不允许驻留在GPU寄存器内存中。我们不会列举所有潜在的约束,也不会指定违反这些约束时的行为当违反约束时,生成适当的信息是一个实现质量问题

"生成适当的信息"的措辞意味着,如果检测到违规,他们希望实现发出警告/出错,而不是返回锁定。

尽管可以回退到锁定的实现可能会愚蠢地将required_alignment设置为正确性(1)的最小值,而不是锁定自由度的最小值。当然,没有人想要这样,但这是一个QoI问题,而不是标准合规性。


我希望(或至少hope)实现工作如下:

  • 如果在alignof小于required_alignment的任何对象上使用atomic_ref,则在编译时发出警告。您可能知道某个T *p恰好是8字节对齐的,即使alignof(T)只有1或4,所以这不应该是错误。

    一些当地的方式来平息警告将是一件好事。(替代方案:承诺与GNU Cx = __builtin_assume_aligned(x, 16)之类的编译器对齐)

    如果一个对象在编译时明确已知对齐不足,至少要发出警告,例如,对齐已知的结构的子成员,或者声明可见但不包括alignas的全局var。通过可能对齐不足的指针进行访问的警告噪音较大,应单独禁用。

  • 超慢调试模式:运行时检查对齐,对原子性对齐不足的特定对象发出警告或中止。(例如gcc -fsanitize=undefined,或者MSVC的调试模式,它已经添加了std::vector::operator[]边界检查之类的东西。我认为GCC的UBSan比MSVC调试模式做更多的检查,例如有符号溢出;我认为MSVC调试方式介于gcc -O0gcc -O0 -fsanitize=undefined之间。)

  • "释放"模式:零检查,只发出asm,其正确性取决于要对齐的对象。(还有gcc-O0,不带UBSan,它允许一致的调试,但不添加额外的检查。)

  • 没有人希望在编译时或运行时静默回退到互斥对象。这种操作模式基本上只是存在的,所以ISO C++可以要求在任何地方都支持该功能,而不会使其无法在某些目标上实现。

    与对为其设计的数据结构同时执行几个相关原子操作的关键部分进行手动细粒度锁定相比,锁定的回退通常是非常次优的。人们使用atomic<T>(以及即将推出的atomic_ref<T>)来提高性能,而大部分性能都被锁定破坏了。尤其是读取端的可扩展性。

脚注1:IIRC,alignof()仅为类型而非对象指定,但在GNU C++中,它也适用于对象。我用这个作为编译器内部知识的简写,即某个对象使用alignas()来过度对齐它