什么是 C++11 原子 API 等价于 ```__asm__ volatile("" ::: "memory")```

Posted

技术标签:

【中文标题】什么是 C++11 原子 API 等价于 ```__asm__ volatile("" ::: "memory")```【英文标题】:What is the C++11 atomic API equivalent to ```__asm__ volatile("" ::: "memory")``` 【发布时间】:2016-11-29 12:54:05 【问题描述】:

代码库有一个COMPILER_BARRIER 宏,定义为__asm__ volatile("" ::: "memory")。宏的目的是防止编译器重新排序跨屏障的读取和写入。请注意,这是明确的编译器屏障,不是处理器级别的内存屏障。

事实上,这是相当可移植的,因为 AssemblerTemplate 中没有实际的汇编指令,只有 volatilememory clobber。因此,只要编译器支持 GCC 的扩展 Asm 语法,它就可以正常工作。不过,如果可能的话,我很好奇在 C++11 原子 API 中表达这一点的正确方法是什么。

以下似乎可能是正确的想法:atomic_signal_fence(memory_order_acq_rel);

我的理由是:

<atomic> API 中,只有 atomic_signal_fenceatomic_thread_fence 不需要内存地址来操作。 atomic_thread_fence 影响内存排序,我们不需要编译器屏障。 Extended Asm 版本中的 memory clobber 不区分读取和写入,因此看起来我们需要获取和释放语义,因此至少需要 memory_order_acq_relmemory_order_seq_cst 似乎没有必要,因为我们不需要跨线程的总顺序 - 我们只对当前线程中的指令顺序感兴趣。

是否可以使用 C++11 原子 API 完全可移植地表达 __asm__ volatile("" ::: "memory") 的等价物?如果是这样,atomic_signal_fence 是使用正确的 API 吗?如果是这样,这里适合/需要什么内存顺序参数?

或者,我是不是陷入了困境,有更好的方法来解决这个问题吗?

【问题讨论】:

atomic_signal_fence 仅保证线程和在同一线程中运行的信号处理程序之间的顺序。同样atomic_thread_fence 仅适用于线程之间的排序。如果您试图保证在其他两个上下文之间进行排序,那么两者都不是可移植的。例如在 Windows 上 atomic_signal_fence 不需要做任何事情,因为 Windows 不支持异步信号。 @RossRidge - 我对使用 atomic_signal_fence 感到有点奇怪,因为正如你所指出的,这里没有信号。但根据我上面的大纲,这是唯一“有效”的东西。我没有在标准中看到任何语言,尽管如果实现没有异步信号,那么可以省略对 atomic_signal_fence 的调用。它确实在 C++14 标准的 28.9.7 中声明“编译器优化和加载和存储的重新排序被禁止以与 atomic_thread_fence 相同的方式,但硬件围栏指令......不会发出。” 这是一个信息性(非规范性)说明,它不会对实施施加限制。该标准没有提供任何允许您依赖它的语言,而不仅仅是“等同于 atomic_thread_fence(order),除了生成的排序约束仅在线程和在同一线程中执行的信号处理程序之间建立” .另请注意,atomic_thread_fence 是根据标准定义的原子对象上的原子操作定义的。因此,如果您不使用 std::atomic 类型,那么这两个函数都不能保证工作。 至少根据标题重复Is there any compiler barrier which is equal to asm("" ::: "memory") in C++11?。 【参考方案1】:

__asm__ volatile("" ::: "memory") 甚至不是一个完整的编译器屏障;它仅强制将加载/存储排序到其地址可被 asm 块访问的对象,其中不包括编译器可以跟踪其地址未泄漏的局部变量。例如,memset(password, 0, len); 后跟 __asm__ volatile("" ::: "memory"); 可能无法将 password[] 使用的内存实际归零。

这可以通过将这些对象的地址作为输入传递给 asm 块来解决,但我没有看到任何与 atomic_signal_fence 完美等价的东西。您可能做的最接近的方法是将对象的地址存储到外部链接volatile 指针对象中(注意创建指针,而不是指向类型,volatile-qualified)然后atomic_signal_fence必须假设它可以从信号处理程序访问。

【讨论】:

这是一个非常有用的观察。这让我想知道宏是否真的做了预期的事情! @acm 和 R.:如果没有 UB,对于地址没有转义函数的对象,从另一个线程或信号处理程序进行观察是不可能的。这就是为什么不需要为它们提供地址并溢出编译器屏障的原因。我认为 GCC 的 atomic_signal_fence 是根据 asm("":::"memory") 实现的,或者至少在编译器内部是等效的。 或者你只是说atomic_signal_fence 没有将额外的本地地址传递给它的机制?当然可以,但它仍然等同于没有 asm("":::"memory") 参数的宏。【参考方案2】:

区分读和写,所以看起来我们想要 获取和释放语义

您似乎混淆了不同的问题。

获取和释放语义都可以对读取和写入产生约束:

release 非正式的意思是之前的内存操作在屏障启动之前已经完成 非正式地获取意味着在屏障完成之前不会开始以下内存操作

然而,这是一个非常简单的解释。 C++ 原子屏障是原子的屏障。它们与原子对象协同工作。当然,线程屏障调用可以自己生成代码,但该代码可以通过一些非原子操作重新排序。

【讨论】:

以上是关于什么是 C++11 原子 API 等价于 ```__asm__ volatile("" ::: "memory")```的主要内容,如果未能解决你的问题,请参考以下文章

C++ 等价于 C# 的内部

在c语言里,if (x)等价于if (x!=0)、if (!x)等价于if (x==0)吗?

等效于嵌入式 C 中的 NOP?

什么是现代的、可移植的、安全的等价于 C 中编译时检查的 memcpy?

Delphi/Cpp 等价于 PHP 中的 RawURLEncode

什么是'import * as ...'等价于require?