Ответ 1
Поскольку вы говорите, что используете GCC и надеетесь поддержать Clang, GCC aligned
attribute должен сделать трюк:
struct foo {
int a, b, c;
} __attribute__((__aligned__(8))); // aligned to 8-byte (64-bit) boundary
Учитывая такое определение структуры, как
struct foo {
int a, b, c;
};
Какой лучший (самый простой, самый надежный и портативный) способ указать, что он всегда должен быть выровнен с 64-разрядным адресом даже в 32-битной сборке? Я использую С++ 11 с GCC 4.5.2 и надеюсь также поддержать Clang.
Поскольку вы говорите, что используете GCC и надеетесь поддержать Clang, GCC aligned
attribute должен сделать трюк:
struct foo {
int a, b, c;
} __attribute__((__aligned__(8))); // aligned to 8-byte (64-bit) boundary
Следующее достаточно переносимо в том смысле, что оно будет работать на множестве различных реализаций, но не на всех:
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
.
Вы должны использовать __attribute__((aligned(8))
. Тем не менее, я нашел это описание только для того, чтобы обеспечить, чтобы выделенный размер структуры был кратным 8 байтам. Он не гарантирует, что начальный адрес является кратным.
Например. Я использую __attribute__((aligned(64))
, malloc может возвращать структуру длиной 64 байта, начальный адрес которой равен 0xed2030.
Если вы хотите, чтобы начальный адрес был выровнен, вы должны использовать aligned_alloc:
gcc aligned allocation. aligned_alloc(64, sizeof(foo)
вернет 0xed2040.
Портативное? Я действительно не знаю о действительно портативном способе. GCC имеет __attribute__((aligned(8)))
, а другие компиляторы могут также иметь эквиваленты, которые можно обнаружить с помощью препроцессорных директив.
Я уверен, что 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;
}
Конечно, в реальном использовании вы бы обернули/скрыли большую часть уродства, которое я показал здесь. Если вы оставите это так, цена (теоретическая/будущая) переносимость, вероятно, будет чрезмерной.