原子变量的对齐

Posted

技术标签:

【中文标题】原子变量的对齐【英文标题】:Alignment of atomic variables 【发布时间】:2014-11-02 19:19:00 【问题描述】:

当然,为了让典型的现代处理器架构(如 x86_64)执行原子加载或存储,要读取/写入的数据需要对齐。

但是这个要求是如何通过 C++11 <atomic> 变量实际实现/强制执行的呢?

假设我有一个支持 16 字节比较和交换(双字 CAS)的架构,因此它可以原子地读取/写入 16 字节值,并且我定义了一个 16 字节类型:

struct double_word

    std::uint64_t x;
    std::uint64_t y;
;

现在,假设我包含一个 std::atomic<double_word> 作为类的成员字段:

class foo

    public:

    std::atomic<double_word> dword;
;

我怎么知道foo::dword 实际上是在 16 字节边界上对齐的?我怎么知道对dword.load() 的调用实际上是 原子的?

实际上,我最初开始问这个问题是因为当我在foo::dword 之前添加另一个数据成员时发生了一件奇怪的事情。我将foo 定义为:

class foo

    public:

    std::uint64_t x;
    std::atomic<double_word> dword; 
;

当我在foo::dword 上实际执行原子加载,并在运行 Debian Linux 的 x86_64 机器上使用 GCC 4.7.2 编译和运行时,它实际上给了我一个分段错误!

完整程序:

#include <atomic>
#include <cstdint>

    struct double_word
    
        std::uint64_t x;
        std::uint64_t y;
    ;

    class foo
    
        public:

        std::uint64_t x;
        std::atomic<double_word> dword; // <-- not aligned on 16-byte boundary
    ;

    int main()
    
        foo f;
        double_word d = f.dword.load(); // <-- segfaults with GCC 4.7.2 !!
    

这实际上是f.dword.load() 上的段错误。起初我不明白为什么,但后来我意识到dword 没有在 16 字节边界上对齐。因此,这会导致很多问题,例如:如果原子变量未对齐并且我们尝试以原子方式加载它,编译器应该怎么做?它是未定义的行为吗?为什么程序只是出现段错误?

其次,C++11 标准对此有何评论?编译器是否应该确保 double_word 在 16 字节边界上自动对齐?如果是这样,这是否意味着 GCC 在这里只是有问题?如果不是 - 似乎由用户来确保对齐,在这种情况下任何时间我们使用大于一个字节的std::atomic&lt;T&gt;,似乎我们会有 使用 std::aligned_storage 确保它正确对齐,这 (A) 看起来很麻烦,(B) 是我在实践或任何示例/教程中从未真正看到过的。

那么,使用 C++11 &lt;atomic&gt;程序员应该如何处理这样的对齐问题?

【问题讨论】:

看起来像是编译器和库之间的合作缺陷。应该通过 C++ 标准段错误工作的代码。 GCC 4.7.2 是旧的,并且在 C++11 标准最终确定之前。请切换到更新版本的GCC(2014 年 11 月,GCC 4.9.2),它将更加符合 C++11。 像任何其他程序员一样,查看编译器手册并了解编译器假设什么以及您应该如何告诉它执行想要的操作。查看编译器手册的另一个原因,因为它们包含更改日志和关于哪些功能不完整的注释……例如 4.7.2 中的 Atomics。 确保std::atomic&lt;T&gt; 适当对齐是实现的责任,就像其他所有标准库对象一样。您的实施有缺陷。 This is GCC bug #62259 正如凯西所说的那样,这是一个老错误 #62259。在 4.9.3 中它不起作用:melpon.org/wandbox/permlink/s3zk69UAj3p26vzJ 但在 5.1 中它已经在起作用:melpon.org/wandbox/permlink/GvirSiavk1jEybeT 【参考方案1】:

这是一个 GCC 错误https://gcc.gnu.org/bugzilla/show_bug.cgi?id=65147 只需添加alignas(16) 即可解决问题。

#include <atomic>
#include <cstdint>

struct double_word

    std::uint64_t x;
    std::uint64_t y;
;

class foo

    public:

    std::uint64_t x;
    alignas(16) std::atomic<double_word> dword; // <-- not aligned on 16-byte boundary
;

int main()

    foo f;
    double_word d = f.dword.load(); // <-- segfaults with GCC 4.7.2 !!

【讨论】:

我无法提出仍然存在问题的简化代码 sn-p,但我观察到在 GCC 8.2.0 中的生产代码中会出现此错误。【参考方案2】:

我认为确保 dword 是 16 字节对齐是程序员的责任。在 64 位平台上,除非您明确指定,否则数据以 64 位边界对齐。

【讨论】:

以上是关于原子变量的对齐的主要内容,如果未能解决你的问题,请参考以下文章

Java并发编程之原子变量

原子变量类

是否存在无法正确对齐 32 位变量的情况

Java多线程之Atomic:原子变量与原子类

2. 原子性 Atomic

Java原子变量类模拟多用户多线程访问