Подтвердить что ты не робот

Указание 64-битного выравнивания

Учитывая такое определение структуры, как

struct foo {
    int a, b, c;
};

Какой лучший (самый простой, самый надежный и портативный) способ указать, что он всегда должен быть выровнен с 64-разрядным адресом даже в 32-битной сборке? Я использую С++ 11 с GCC 4.5.2 и надеюсь также поддержать Clang.

4b9b3361

Ответ 1

Поскольку вы говорите, что используете GCC и надеетесь поддержать Clang, GCC aligned attribute должен сделать трюк:

struct foo {
    int a, b, c;
} __attribute__((__aligned__(8))); // aligned to 8-byte (64-bit) boundary

Ответ 2

Следующее достаточно переносимо в том смысле, что оно будет работать на множестве различных реализаций, но не на всех:

union foo {
    struct {int a, b, c; } data;
    double padding1;
    long long padding2;
};

static char assert_foo_size[sizeof(foo) % 8 == 0 ? 1 : -1];

Это не скомпилируется, если только:

  • компилятор добавил некоторое дополнение к foo, чтобы довести его до кратного 8, что обычно происходит только из-за требования выравнивания или
  • расположение foo.data крайне странно, или
  • один из long long и double больше, чем 3 ints, и кратный 8. Это не обязательно означает, что он выровнен по 8.

Учитывая, что вам нужно только поддерживать 2 компилятора, и clang довольно gcc-совместим по дизайну, просто используйте __attribute__, который работает. Только подумайте о том, чтобы делать что-нибудь еще, если вы хотите написать код, который будет (надеюсь) работать над компиляторами, которые вы не тестируете.

С++ 11 добавляет alignof, который вы можете проверить вместо проверки размера. Он удалит ложные срабатывания, но все равно оставит вас с некоторыми соответствующими реализациями, на которых профсоюз не сможет создать нужное вам выравнивание и, следовательно, не скомпилируется. Кроме того, мой трюк sizeof довольно ограничен, это не помогает, если ваша структура имеет 4 int вместо 3, тогда как то же самое с alignof делает. Я не знаю, какие версии gcc и clang поддерживают alignof, поэтому я не использовал его для начала. Я бы не подумал, что это трудно сделать.

Кстати, если экземпляры foo динамически распределяются, все становится проще. Во-первых, я подозреваю, что реализация glibc или аналогичных malloc будет 8-выровнять в любом случае - если есть базовый тип с 8-байтовым выравниванием, то malloc должен, и я думаю, что glibc malloc просто всегда, а не беспокоясь о том, есть или нет на какой-либо данной платформе. Во-вторых, там posix_memalign.

Ответ 3

Вы должны использовать __attribute__((aligned(8)). Тем не менее, я нашел это описание только для того, чтобы обеспечить, чтобы выделенный размер структуры был кратным 8 байтам. Он не гарантирует, что начальный адрес является кратным.

Например. Я использую __attribute__((aligned(64)), malloc может возвращать структуру длиной 64 байта, начальный адрес которой равен 0xed2030.

Если вы хотите, чтобы начальный адрес был выровнен, вы должны использовать aligned_alloc: gcc aligned allocation. aligned_alloc(64, sizeof(foo) вернет 0xed2040.

Ответ 4

Портативное? Я действительно не знаю о действительно портативном способе. GCC имеет __attribute__((aligned(8))), а другие компиляторы могут также иметь эквиваленты, которые можно обнаружить с помощью препроцессорных директив.

Ответ 5

Я уверен, что gcc 4.5.2 достаточно стар, что он еще не поддерживает стандартную версию, но С++ 11 добавляет некоторые типы, специально предназначенные для выравнивания - std::aligned_storage и std::aligned_union среди других (подробнее см. § 20.9.7.6).

Мне кажется, что наиболее очевидный способ сделать это - использовать реализацию Boost aligned_storage (или TR1, если у вас есть). Если вы этого не хотите, я все равно буду стараться использовать стандартную версию в большинстве ваших кодов и просто напишу небольшую реализацию для своего собственного использования, пока вы не обновите компилятор, который реализует стандарт. Однако переносимый код по-прежнему будет немного отличаться от большинства, что напрямую использует что-то вроде __declspec(align... или __attribute__(__aligned__, ....

В частности, он просто дает вам необработанный буфер запрошенного размера с запрошенным выравниванием. тогда вам нужно использовать что-то вроде размещения new для создания объекта вашего типа в этом хранилище.

Для чего это стоит, быстро надавите на реализацию aligned_storage на основе директивы gcc __attribute__(__aligned__,...:

template <std::size_t Len, std::size_t Alignment>
struct aligned_storage {
    typedef struct {
        __attribute__(__aligned__(Alignment)) unsigned char __data[Len];
    } type;
};

Быстрая тестовая программа, чтобы показать, как это использовать:

struct foo {
    int a, b, c;

    void *operator new(size_t, void *in) { return in; }
};

int main() {
    stdx::aligned_storage<sizeof(foo), 8>::type buf;

    foo& f = *new (static_cast<void*>(&buf)) foo();

    int address = *reinterpret_cast<int *>(&f);

    if (address & 0x3 != 0)
        std::cout << "Failed.\n";

    f.~foo();

    return 0;
}

Конечно, в реальном использовании вы бы обернули/скрыли большую часть уродства, которое я показал здесь. Если вы оставите это так, цена (теоретическая/будущая) переносимость, вероятно, будет чрезмерной.