Можно ли использовать один блок памяти для хранения нескольких объектов?

Я реализую очень странную структуру, в которой я выделяю один фрагмент памяти и сохраняю в нем несколько объектов разного типа:

auto memory = reinterpret_cast<std::uintptr_t>(::operator new(size));
new(reinterpret_cast<void*>(memory)) Class1();
new(reinterpret_cast<void*>(memory + offset)) Class2();

Мое беспокойство, этот код нарушает строгие правила наложения имен?

Что если я перепишу этот код следующим образом:

void* memory = ::operator new(size);
new(memory) Class1();
new(reinterpret_cast<void*>(reinterpret_cast<char*>(memory) + offset)) Class2();

При условии size гарантированно будет достаточно большим, memory + offset гарантированно правильно выровнены, оба конструктора класса объявлены nothrow, а деструкторы обоих классов вызываются при освобождении памяти, вводит ли этот код UB? С какими еще проблемами я могу столкнуться с таким кодом?

1 ответ

Решение

Отвечая на ваш вопрос

Мое беспокойство, этот код нарушает строгие правила наложения имен?

Нет не

Давайте сначала кое-что поймем.

Что такое псевдоним?

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

int anint;
int *intptr=&anint;

Почему правила псевдонимов вообще были введены?

До введения строгого псевдонима компилятор должен был жить в состоянии паранойи, когда содержимое баффа может измениться в любое время и из любого места. Таким образом, чтобы получить дополнительное преимущество в производительности, и при условии, что большинство людей не вводят указатели с каламбура, было введено строгое правило наложения имен.

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

Как правильно упомянуть @Lightness

возможно, именно поэтому размещение новых существует в первую очередь

Размещение нового позволяет вам построить объект в памяти, которая уже выделена. И вы можете захотеть сделать это для оптимизации (быстрее не перераспределять все время), но вам нужно перестроить объект несколько раз. Если вам нужно продолжать перераспределение, может быть более эффективно распределить больше, чем нужно, даже если вы еще не хотите его использовать.

С какими еще проблемами я могу столкнуться с таким кодом? И что, если я переписываю этот код следующим образом:

 void* memory = ::operator new(size);
    new(memory) Class1();
    new(reinterpret_cast<void*>(reinterpret_cast<char*>(memory) + offset)) Class2();

Будьте уверены, что компилятор пометит вам некоторые предупреждения.

Примечание. Чтобы как можно быстрее обнаружить проблемы с алиасами, -fstrict-aliasing всегда должны быть включены в флаги компиляции для GCC. В противном случае проблемы могут быть видны только на самых высоких уровнях оптимизации, где их сложнее всего отлаживать.

Возможно, вы захотите взглянуть на Endianness, Понимание строгого псевдонима в C/C++ и какие существуют варианты "размещения новых"?

Другие вопросы по тегам