Важно ли заполнение при вызове нового размещения?

Я хочу построить B на месте A.

struct A {
  size_t x;  // 8 bytes
  int i;     // 4 bytes
};           // padding 4 bytes

struct B {
  size_t x;  // 8 bytes
  bool b;    // 1 byte
};           // padding 7 bytes

int main() {
  A a;
  auto p = new(&a) B;
  // auto p = new(std::launder(&a)) B;  // is a bit better
}

Этот код четко определен? Будет ли placement new касаться байтов заполнения (например, устанавливать все нули) или будет работать напрямую с b байтами?

Например, две структуры A хранятся рядом (сразу после первой A::i идет вторая A::x). Если placement new коснется отступа, данные второго A будут повреждены.

Я не думаю, что можно трогать отступы, поскольку структура легко конструируется. Но формально это может быть УБ, а может и не быть, я не уверен (вероятно, на практике безвредно). Меня больше беспокоит несовпадение структур, но я думаю, что на большинстве платформ это тоже должно быть нормально?

HolyBlackCat 22.04.2024 17:50

Выделение большего количества байтов, чем размер A, является неопределенным поведением. Заполнение зависит от настроек компилятора и платформы, поэтому в лучшем случае вы получите что-то, что работает на вашей машине. Поэтому я бы даже не осмелился полагаться на упомянутые вами значения заполнения (и предположение, что sizeof size_t и указатель равны). Особенно нет, если код должен быть переносимым.

Pepijn Kramer 22.04.2024 17:51

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

Pepijn Kramer 22.04.2024 17:52

@PepijnKramer это связано с решением stackoverflow.com/q/78365893/23545465

Alexander S 22.04.2024 17:54

«For example, two A structures are stored contiguously (right after first A::i goes second A::st)» Нет, это не так. A независимо от того имеет отступы.

Mooing Duck 22.04.2024 18:27
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
5
87
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Здесь есть несколько вещей, которые стоит обсудить.

  • Во-первых, заполнение обычно не указано, поэтому не следует предполагать, что вы знаете, что и где находится. Основные ограничения заключаются в том, что

    • каждый скалярный подобъект имеет размер и выравнивание, которые необходимо учитывать, и
    • массив типа T[N] имеет размер N * sizeof(T), т. е. нет «дополнительного заполнения массива». Это дает вам минимальные последствия заполнения для T.
  • Это означает, что ваше «сразу после того, как первый A::i идет вторым A::st» не может быть выполнено; это не то, что вам решать.

  • Размещение-новое может повторно использовать существующее хранилище, и основное требование состоит в том, чтобы хранилище было достаточно большим и соответствующим образом выровнено для типа назначения. Вы можете статически утверждать эти вещи с помощью sizeof и alignof. Результатом выражения Place-new является то, что целевой объект (типа B в вашем примере) инициализируется (в соответствии с выбранным синтаксисом инициализации), который потенциально будет записывать в любое и все хранилище, занимаемое объектом B. В соответствии с вышеуказанными требованиями они будут полностью храниться в предоставленном хранилище, или у вас уже есть UB.

  • Если вы хотите, чтобы целый массив A[N] обеспечивал хранилище для некоторой последовательности объектов, то, конечно, в принципе возможно перезаписать хранилище нескольких соседних A объектов, но при условии, что каждый B объект будет заново сконструирован в один конкретный A объект и что он предоставляет достаточно места для хранения (как описано выше), то ни один B объект не сделает недействительными несвязанные базовые A объекты.

Если вы не контролируете то, что «клиент» помещает в вашу структуру данных, все еще есть вещи, связанные с объединениями/вариантами и константностью, которые следует учитывать. И это может быть довольно сложно, прочитайте еще немного о std::launder: stackoverflow.com/questions/39382501/…. Мне бы очень хотелось знать, какие «требования клиента» побуждают вас отправиться в страну «здесь драконы» ;) Для меня это был бы один из тех случаев... давайте вернемся к клиенту и поговорим с ним еще раз.

Pepijn Kramer 22.04.2024 19:28

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