Естественно, для того, чтобы типичная современная архитектура процессора (например, x86_64) выполняла атомную нагрузку или хранилище, данные, подлежащие чтению/записи, должны быть выровнены.
Но как это требование реализуется/реализуется с помощью переменных С++ 11 <atomic>
?
Предположим, что у меня есть архитектура, поддерживающая 16-байтовое сравнение и обмен (double-word 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
, а компилирую и запускаю с использованием GCC 4.7.2 на машине x86_64, на которой запущен Debian Linux, это фактически дает мне ошибку сегментации!
Полная программа:
#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-байтной границе. Таким образом, это приводит к множеству вопросов, таких как: что должен делать компилятор, если атомная переменная не выровнена, и мы пытаемся ее атомизировать? Это поведение undefined? Почему программа просто разделилась?
Во-вторых, что говорит об этом стандарт С++ 11? Должен ли компилятор убедиться, что double_word
автоматически выровнен по 16-байтовой границе? Если да, значит ли это, что GCC здесь просто глючит? Если нет - казалось бы, пользователь должен обеспечить выравнивание, и в этом случае в любое время, когда мы используем std::atomic<T>
больше одного байта, казалось бы, нам нужно будет использовать std::aligned_storage
, чтобы убедиться, что он правильно выровнен, который (A) кажется громоздким, и (B) - это то, что я никогда не видел на практике или в каких-либо примерах/учебниках.
Итак, как программист, использующий С++ 11 <atomic>
, обрабатывает такие проблемы выравнивания?