Выравнивание данных в стеке (С++)

Этот вопрос специфичен для компилятора MSVC (в частности, 2008), но я также заинтересован в ответах, отличных от компилятора.

Я пытаюсь выяснить, как выровнять буфер char в стеке, основываясь на выравнивании некоторого произвольного типа. В идеале код будет читать:

__declspec( align( __alignof(MyType) ) ) char buffer[16*sizeof(MyType)];

К сожалению, это не работает

ошибка C2059: синтаксическая ошибка: '__builtin_alignof'

Компилятор просто не любит вложенные операторы.

Моя единственная идея - сделать это:

char buffer[16*sizeof(MyType)+__alignof(MyType)-1];
char * alignedBuffer = (char*)((((unsigned long)buffer) + __alignof(MyType)-1)&~(__alignof(MyType)-1));

Кто-нибудь знает, что лучше? Кажется, что функция declspec должна работать, просто я имею синтаксис неправильно или что-то в этом роде?

Спасибо за чтение:)

Ответы

Ответ 1

Обновление

Отметьте ответ Роберта Найт! Использует С++ 11, но намного чище, чем это...


Оригинальный ответ

Как насчет этого неприятного взлома:

namespace priv {

#define PRIVATE_STATICMEM(_A_) \
    template <size_t size> \
    struct StaticMem<size,_A_> { \
      __declspec(align(_A_)) char data[size]; \
      void *operator new(size_t parSize) { \
        return _aligned_malloc(parSize,_A_); \
      } \
      void operator delete(void *ptr) { \
        return _aligned_free(ptr); \
      } \
    };

    template <size_t size, size_t align> struct StaticMem {};
    template <size_t size> struct StaticMem<size,1> {char data[size];};

    PRIVATE_STATICMEM(2)
    PRIVATE_STATICMEM(4)
    PRIVATE_STATICMEM(8)
    PRIVATE_STATICMEM(16)
    PRIVATE_STATICMEM(32)
    PRIVATE_STATICMEM(64)
    PRIVATE_STATICMEM(128)
    PRIVATE_STATICMEM(256)
    PRIVATE_STATICMEM(512)
    PRIVATE_STATICMEM(1024)
    PRIVATE_STATICMEM(2048)
    PRIVATE_STATICMEM(4096)
    PRIVATE_STATICMEM(8192)

}

template <typename T, size_t size> struct StaticMem : public priv::StaticMem<sizeof(T)*size,__alignof(T)> {
    T *unhack() {return (T*)this;}
    T &unhack(size_t idx) {return *(T*)(data+idx*sizeof(T));}
    const T &unhack() const {return *(const T*)this;}
    const T &unhack(size_t idx) const {return *(const T*)(data+idx*sizeof(T));}
    StaticMem() {}
    StaticMem(const T &init) {unhack()=init;}
};

Выглядит страшно, но вам нужно все это только один раз (желательно в некотором хорошо скрытом файле заголовка:)). Затем вы можете использовать его следующим образом:

StaticMem<T,N> array; //allocate an uninitialized array of size N for type T
array.data //this is a raw char array
array.unhack() //this is a reference to first T object in the array
array.unhack(5) //reference to 5th T object in the array

StaticMem<T,N> array; может отображаться в коде, но также и как член какого-либо более крупного класса (как я использую этот хак), а также должен корректно вести себя, когда он выделяется в куче.

Исправление ошибок:

Строка 6 примера: char data[_A_] исправлена ​​в char data[size]

Ответ 2

Вы можете использовать std::aligned_storage вместе с std::alignment_of в качестве альтернативы.

#include <type_traits>

template <class T, int N>
struct AlignedStorage
{
    typename std::aligned_storage<sizeof(T) * N, std::alignment_of<T>::value>::type data;
};

AlignedStorage<int, 16> myValue;

Это поддерживается MSVC 2008 и выше. Если вам нужна переносимость для других компиляторов, отличных от С++ 11, вы можете использовать std::tr1::aligned_storage и std::tr1::alignment_of и заголовок <tr1/type_traits>.

В приведенном выше коде AlignedStorage<T>::data будет представлять собой тип POD (массив char [] в MSVC и GCC) подходящего выравнивания для T и размера T * N.

Ответ 3

Вы уверены, что MyType - допустимая целая мощность?

__declspec( align( # ) ) declarator

# - значение выравнивания. Допустимыми значениями являются целые степени, равные двум из От 1 до 8192 (байт), таких как 2, 4, 8, 16, 32 или 64. декларатором являются данные которые вы объявляете как выровненные.

- align (С++)

Ответ 4

Как насчет alloca()? (Или, более конкретно, для MSVC2008, _ malloca())?

Выделяет память в стеке. Это версия _alloca с улучшениями безопасности, как описано в разделе "Улучшения безопасности" в CRT.

Поведение сопоставления не стандартизировано для всех компиляторов, но для этого...

Процедура _malloca возвращает указатель void на выделенное пространство, которое, как гарантируется, будет соответствующим образом выровнено для хранения любого типа объекта.

Ответ 5

Была та же проблема. Вы можете смять макрос (oh horrors), который сочетает align и __alignof безопасным способом:

// `align` und `__alignof` cannot be combined - hence this workaround where you also have to specify the alignment manually (but checked)
#define ALIGN_FOR_TYPE( TypeName, TypeAlignment )                           \
    const size_t ALIGN_FOR_TYPE_alignOf##TypeName = __alignof(TypeName);    \
    BOOST_STATIC_ASSERT(ALIGN_FOR_TYPE_alignOf##TypeName == TypeAlignment); \
    __declspec( align( TypeAlignment ) )                                    \
/**/

ALIGN_FOR_TYPE(MyStructType, 4) char StructBuffer[sizeof(MyStructType)];

Ответ 6

Вам не нужно выполнять никаких дополнительных "хаков", если вы хотите объявить выровненные данные в стеке. Компилятор позаботится об этом.

Если вы хотите, чтобы это был массив char, просто добавьте его в char*

Попробуйте выполнить следующий тестовый пример:

#include <stdio.h>

struct UnalignedX {
    int x;
};

__declspec(align(128)) struct AlignedX {
    int x;
};

int main() {
    UnalignedX arr[5];
    AlignedX aarr[5];
    printf("UnalignedX: %x %x\n",arr,arr+1);
    printf("AlignedX: %x %x\n",aarr,aarr+1);
    char *final=(char*)aarr; //this becomes the char array that you asked for
    return 0;
};

На моем компьютере я получил вывод:

UnalignedX: 14fe68 14fe6c
AlignedX: 14fb80 14fc00

Вы должны быть осторожны с выравниванием при распределении данных в куче (либо malloc или new)

__declspec( align( N )) ожидает, что N будет литералом. Он должен быть числом, а не вызовом функции.