2012-06-08 3 views
12

Учитывая определение структуры какЗадание 64-бит выравнивания

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

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

+0

Мне любопытно; почему имеет значение, что выравнивание находится на 32-битной системе? Или, действительно, в 64-битной системе, поскольку эта структура обычно не должна быть более 32-битной. –

+0

После того, как компиляторы поддерживают его, вы можете использовать alignas. – PlasmaHH

+0

@JonathanLefler: Я бы предположил, что допускает автоматическую оптимизацию sse. gcc недавно добавила некоторую __builtin_assume_aligned, чтобы сообщить компилятору, что материал должен быть выровнен. Игра с примерами http://gcc.godbolt.org/ может дать больше понимания. – PlasmaHH

ответ

14

Так вы говорите, что вы используете GCC и надеясь на поддержку Clang, GCC-х alignedattribute должен сделать трюк:

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

Это не переносимо.Но тогда ничего не будет. –

+1

@JohnDibling: Я знаю. Он переносится для двух компиляторов, о которых идет речь. – Fanael

+0

Должно ли это быть __ атрибут __ ((выровненный (8))), в соответствии с документом, который вы связали? – D0SBoots

1

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

7

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

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 целых числа, и кратное 8. Это не обязательно означает, что оно выровнено по 8.

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

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

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

+0

+1 Очень приятно (без каких-либо неприятных расширений компилятора). Можно даже сделать анонимную структуру 'data', чтобы получить поведение, подобное исходной структуре OP. –

+0

Хорошее решение для определенных наборов платформ/компиляторов. Если истинная переносимость - ваша цель, бинарная совместимость сериализованных данных, вероятно, не должна быть дополнительной целью. – cptstubing06

+0

Почему двойной/длинный длинный ??? uint64_t можно использовать более безопасно, кроме того, прокладка может быть скрыта с помощью битового поля: 'uint64_t: 0;' – Aconcagua

1

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

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

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

Для чего это стоит, вот быстрый удар на реализацию aligned_storage на основе __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; 
} 

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

2

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

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

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

Смежные вопросы