【发布时间】: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<T>,似乎我们会有 使用 std::aligned_storage 确保它正确对齐,这 (A) 看起来很麻烦,(B) 是我在实践或任何示例/教程中从未真正看到过的。
那么,使用 C++11 <atomic> 的程序员应该如何处理这样的对齐问题?
【问题讨论】:
-
看起来像是编译器和库之间的合作缺陷。应该通过 C++ 标准段错误工作的代码。
-
像任何其他程序员一样,查看编译器手册并了解编译器假设什么以及您应该如何告诉它执行您想要的操作。查看编译器手册的另一个原因,因为它们包含更改日志和关于哪些功能不完整的注释……例如 4.7.2 中的 Atomics。
-
确保
std::atomic<T>适当对齐是实现的责任,就像其他所有标准库对象一样。您的实施有缺陷。 This is GCC bug #62259 -
正如凯西所说的那样,这是一个老错误 #62259。在 4.9.3 中它不起作用:melpon.org/wandbox/permlink/s3zk69UAj3p26vzJ 但在 5.1 中它已经在起作用:melpon.org/wandbox/permlink/GvirSiavk1jEybeT