Выравнивание атомных переменных

Естественно, для того, чтобы типичная современная архитектура процессора (например, 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>, обрабатывает такие проблемы выравнивания?

Ответ 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 !!
}

Ответ 2

Я думаю, что ответственность программиста заключается в том, чтобы гарантировать, что dword - выравнивание по 16 байт. В 64-битной платформе данные выравниваются по границе 64 бит, если вы не укажете явно.