массив символов как хранилище для размещения новых

Является ли следующий допустимый C ++ четко определенным поведением?

class my_class { ... };

int main()
{
    char storage[sizeof(my_class)];
    new ((void *)storage) my_class();
}

Или это проблематично из-за соображений приведения / выравнивания указателя?


person bluescarni    schedule 03.01.2011    source источник
comment
Для меня это нормально.   -  person Neigyl R. Noval    schedule 03.01.2011
comment
Нет, многоточие в этом контексте недопустимо ... (Подсказка: если вы хотите спросить, хорошо ли определен код, сначала его нужно скомпилировать.)   -  person GManNickG    schedule 03.01.2011
comment
Кто-нибудь, пожалуйста, скажите мне, как можно использовать вышеуказанные вещи в реальном мире программирования.   -  person vrbilgi    schedule 03.01.2011
comment
@GMan: Я очень надеюсь, что ты просто шутишь. Да, четкость зависит от пропущенного раздела, но очевидно, в чем вопрос.   -  person Jon Purdy    schedule 03.01.2011
comment
@Jon: Нет, я не шучу. Лучше всего, если вопрос будет ясным, без двусмысленности. (И нет, его четкое определение не зависит от пропущенного раздела; у вас просто нет такой гарантии.)   -  person GManNickG    schedule 03.01.2011
comment
Я просто хотел бы знать, почему кто-то захочет это сделать   -  person David Heffernan    schedule 03.01.2011
comment
@ user430294 @ Дэвид Хеффернан: Херб Саттер написал статью GotW (№ 28) о Идиома pimpl, которая использует что-то вроде того, что написал OP. Затем он не рекомендует этого делать, потому что не гарантируется, что массив будет выровнен, в отличие от динамической памяти. Короче: не делайте этого.   -  person In silico    schedule 03.01.2011
comment
@ user430294 @ Дэвид Хеффернан: цель в основном состоит в том, чтобы выделить хранилище для объекта, но построить объект только по мере необходимости. И в то же время с сохранением схемы доступа к памяти, удобной для кеширования (т.е. без динамического выделения).   -  person bluescarni    schedule 03.01.2011
comment
@bluescarni, верно, потому что нельзя использовать динамическое размещение!   -  person David Heffernan    schedule 03.01.2011


Ответы (5)


Да, это проблематично. У вас просто нет гарантии, что память правильно выровнена.

Хотя существуют различные уловки для получения хранилища с правильным выравниванием, лучше всего использовать Boost или C ++ 0x aligned_storage, которые скрывают эти уловки от вас.

Тогда вам просто необходимо:

// C++0x
typedef std::aligned_storage<sizeof(my_class),
                                alignof(my_class)>::type storage_type;

// Boost
typedef boost::aligned_storage<sizeof(my_class),
                        boost::alignment_of<my_class>::value>::type storage_type;

storage_type storage; // properly aligned
new (&storage) my_class(); // okay

Обратите внимание, что в C ++ 0x, используя атрибуты, вы можете просто сделать это:

char storage [[align(my_class)]] [sizeof(my_class)];
person GManNickG    schedule 03.01.2011
comment
Спасибо, это очень полезно знать. - person bluescarni; 03.01.2011
comment
Извините, пока не совсем :) Насколько я понял, тип хранилища - это какой-то встроенный целочисленный тип. Как мне его использовать? Могу ли я просто привести к типу my_class через void *? - person bluescarni; 03.01.2011
comment
Извините, я хотел сказать: свободно переводить туда и обратно указатели на экземпляры my_class ... Или что-то в этом роде :) - person bluescarni; 03.01.2011
comment
@bluescarni: Ах, прости. Мой ответ неполный: результирующий тип должен использоваться как блок памяти, поэтому он должен быть адресом этого хранилища. Я исправил это. - person GManNickG; 03.01.2011

Как уже упоминалось здесь, это не обязательно сработает из-за ограничений выравнивания. Есть несколько способов добиться правильного выравнивания. Во-первых, если у вас есть компилятор, совместимый с C ++ 0x, вы можете использовать оператор alignof, чтобы попытаться заставить выравнивание быть правильным. Во-вторых, вы можете динамически выделять массив символов, поскольку память от оператора new гарантированно будет выровнена таким образом, чтобы что угодно могло ее правильно использовать. В-третьих, вы можете попробовать сохранить массив символов в объединении с некоторым типом, который имеет максимально возможное выравнивание в вашей системе; Я считаю, что эта статья содержит некоторую информацию по этому поводу (хотя она разработана для C ++ 03 и, конечно, не так же хорошо, как и оператор alignof, который скоро выйдет).

Надеюсь это поможет!

person templatetypedef    schedule 03.01.2011

Это как минимум проблематично из-за выравнивания.

В большинстве архитектур, отличных от Intel, код будет генерировать «ошибку шины» из-за неправильного выравнивания или будет работать очень медленно из-за ловушек процессора, необходимых для исправления невыровненного доступа к памяти.

На архитектуре Intel это обычно будет немного медленнее, чем обычно. За исключением случаев, когда задействованы некоторые операции SSE, это также может привести к сбою.

person Markus Kull    schedule 03.01.2011

Если кто-то хочет избежать Boost или C ++ 1x, этот полный код работает как в GCC, так и в MSVC. Специфичный для MSVC код основан на align_memory.h. Это немного сложнее, чем версия GCC, потому что __declspec(align(.)) MSVC принимает только литеральные значения выравнивания, и это обходится с использованием специализации шаблона для всех возможных выравниваний.

#ifdef _MSC_VER

template <size_t Size, size_t Align>
struct AlignedMemory;

#define DECLARE_ONE_ALIGNED_MEMORY(alignment) \
template <size_t Size> \
struct __declspec(align(alignment)) AlignedMemory<Size, alignment> { \
    char mem[Size]; \
};

DECLARE_ONE_ALIGNED_MEMORY(1)
DECLARE_ONE_ALIGNED_MEMORY(2)
DECLARE_ONE_ALIGNED_MEMORY(4)
DECLARE_ONE_ALIGNED_MEMORY(8)
DECLARE_ONE_ALIGNED_MEMORY(16)
DECLARE_ONE_ALIGNED_MEMORY(32)
DECLARE_ONE_ALIGNED_MEMORY(64)
DECLARE_ONE_ALIGNED_MEMORY(128)
DECLARE_ONE_ALIGNED_MEMORY(256)
DECLARE_ONE_ALIGNED_MEMORY(512)
DECLARE_ONE_ALIGNED_MEMORY(1024)
DECLARE_ONE_ALIGNED_MEMORY(2048)
DECLARE_ONE_ALIGNED_MEMORY(4096)

#else

template <size_t Size, size_t Align>
struct AlignedMemory {
    char mem[Size];
} __attribute__((aligned(Align)));

#endif

template <class T>
struct AlignedMemoryFor : public AlignedMemory<sizeof(T), __alignof(T)> {};
person Ambroz Bizjak    schedule 28.07.2012

Массив char может быть неправильно выровнен для размера myclass. На некоторых архитектурах это означает более медленный доступ, а на других - сбой. Вместо char вы должны использовать тип, выравнивание которого равно или больше, чем у struct, которое задается наибольшим требованием выравнивания любого из его членов.

#include <stdint.h>

class my_class { int x; };

int main() {
    uint32_t storage[size];
    new(storage) my_class();
}

Чтобы выделить достаточно памяти для одного my_class экземпляра, я думаю, что size должно быть sizeof(my_class) / sizeof(T), где T - это тот тип, который вы используете для правильного выравнивания.

person Jon Purdy    schedule 03.01.2011
comment
... чье выравнивание равно или больше, чем выравнивание структуры, которое задается наибольшим требованием выравнивания любого из ее членов, я не думаю, что это гарантировано, выравнивание полностью определяется реализацией. (То есть, это выравнивание не обязательно должно быть выравниванием самого строгого члена.) Кроме того, int может быть больше 32 бит. - person GManNickG; 03.01.2011
comment
@GMan: Ты прав. Однако в большинстве случаев это работает. Здесь есть ссылка: stackoverflow.com/questions/364483/ - person Jon Purdy; 03.01.2011