Размещение массива-new требует неопределенных накладных расходов в буфере?

5.3.4 [expr.new] проекта С++ 11 февраля приведен пример:

new(2,f) T[5] приводит к вызову operator new[](sizeof(T)*5+y,2,f).

Здесь x и y - неотрицательные неопределенные значения, представляющие издержки распределения массива; результат нового выражения будет компенсирован этой суммой из значения, возвращаемого operator new[]. Эти служебные данные могут применяться во всех новых выражениях массива, включая те, которые ссылаются на библиотечную функцию operator new[](std::size_t, void*) и другие функции распределения мест. Количество накладных расходов может варьироваться от одного вызова нового к другому. -end пример]

Теперь возьмите следующий пример кода:

void* buffer = malloc(sizeof(std::string) * 10);
std::string* p = ::new (buffer) std::string[10];

В соответствии с приведенной выше цитатой вторая строка new (buffer) std::string[10] будет внутренне вызывать operator new[](sizeof(std::string) * 10 + y, buffer) (до создания отдельных объектов std::string). Проблема в том, что если y > 0, предварительно выделенный буфер будет слишком маленьким!

Итак, как я узнаю, сколько памяти нужно предварительно выделить при использовании размещения массива new?

void* buffer = malloc(sizeof(std::string) * 10 + how_much_additional_space);
std::string* p = ::new (buffer) std::string[10];

Или стандарт в этом случае гарантирует, что y == 0 в этом случае? Опять же, в цитате сказано:

Эти служебные данные могут применяться во всех новых выражениях массива, включая те, которые ссылаются на библиотечную функцию operator new[](std::size_t, void*) и другие функции распределения мест.

Ответ 1

Не используйте operator new[](std::size_t, void* p), если вы не знаете априорный ответ на этот вопрос. Ответ - это деталь реализации и может измениться с помощью компилятора/платформы. Хотя он обычно стабилен для любой данной платформы. Например. это то, что указано Itanium ABI.

Если вы не знаете ответа на этот вопрос, напишите свой собственный массив размещения new, который может проверить это во время выполнения:

inline
void*
operator new[](std::size_t n, void* p, std::size_t limit)
{
    if (n <= limit)
        std::cout << "life is good\n";
    else
        throw std::bad_alloc();
    return p;
}

int main()
{
    alignas(std::string) char buffer[100];
    std::string* p = new(buffer, sizeof(buffer)) std::string[3];
}

Изменяя размер массива и проверяя n в приведенном выше примере, вы можете сделать вывод y для своей платформы. Для моя платформа y - это одно слово. Sizeof (word) изменяется в зависимости от того, компилирую ли я для 32-битной или 64-битной архитектуры.

Ответ 2

Обновление:. После некоторого обсуждения я понимаю, что мой ответ больше не применяется к вопросу. Я оставлю его здесь, но реальный ответ определенно по-прежнему требует.

Я буду рад поддержать этот вопрос с некоторой щедростью, если хороший ответ не будет найден в ближайшее время.

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

Правильно ли построена следующая конструкция? Является ли arr == addr в конце?

void * addr = std::malloc(N * sizeof(T));
T * arr = ::new (addr) T[N];                // #1

Мы знаем из стандарта, что # 1 вызывает вызов ::operator new[](???, addr), где ??? - неуказанное число, не меньшее N * sizeof(T), и мы также знаем, что этот вызов возвращает только addr и не имеет других эффектов, Мы также знаем, что arr соответственно смещается от addr. То, что мы не знаем, является ли объем памяти, на который указывает addr, достаточно большой или как мы будем знать, сколько памяти выделяется.


Вы, кажется, путаете несколько вещей:

  • В вашем примере вызывается operator new[](), а не operator new().

  • Функции распределения ничего не создают. Они выделяют.

Случается, что выражение T * p = new T[10]; вызывает:

  • вызов operator new[]() с аргументом размера 10 * sizeof(T) + x,

  • десять вызовов конструктора по умолчанию T, эффективно ::new (p + i) T().

Единственная особенность заключается в том, что новое выражение массива запрашивает больше памяти, чем то, что используется самими данными массива. Вы не видите ничего подобного и не можете использовать эту информацию каким-либо образом, кроме как молчаливым согласием.


Если вам интересно, сколько памяти было фактически выделено, вы можете просто заменить функции выделения массива operator new[] и operator delete[] и распечатать фактический размер.


Обновление. Как случайная информация, вы должны заметить, что глобальные функции размещения не требуются. То есть, когда вы строите объект или массив на месте так:

T * p = ::new (buf1) T;
T * arr = ::new (buf10) T[10];

Затем соответствующие вызовы ::operator new(std::size_t, void*) и ::operator new[](std::size_t, void*) не делают ничего, кроме как возвращают их второй аргумент. Однако вы не знаете, что должен указывать buf10: он должен указывать на 10 * sizeof(T) + y байты памяти, но вы не можете знать y.

Ответ 3

Вызов любой версии operator new[] () не будет работать слишком хорошо с областью памяти фиксированного размера. По сути, предполагается, что он делегирует некоторую реальную функцию распределения памяти, а не просто возвращает указатель на выделенную память. Если у вас уже есть арена памяти, где вы хотите построить массив объектов, вы хотите использовать std::uninitialized_fill() или std::uninitialized_copy() для создания объектов (или какой-либо другой формы индивидуальной конструирования объектов).

Вы можете утверждать, что это означает, что вам также необходимо вручную уничтожить объекты на арене памяти. Однако вызов delete[] array в указателе, возвращенном из места размещения new, не будет работать: он будет использовать версию без размещения operator delete[] ()! То есть при использовании места размещения new вам необходимо вручную уничтожить объект и освободить память.

Ответ 4

Как упомянуто Керреком С.Б. в комментариях, этот дефект был впервые зарегистрирован в 2004 году, и он был устранен в 2012 году как:

CWG решила, что EWG является подходящим местом для решения этой проблемы.

Затем о дефекте сообщили в EWG в 2013 году, но он был закрыт как NAD (предположительно означает "не дефект") с комментарием:

Проблема заключается в попытке использовать массив new для помещения массива в существующее хранилище. Нам не нужно использовать массив new для этого; просто сконструируйте их.

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


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

T *ptr = new T[N];
::operator delete[](ptr);

Даже если мы соблюдаем правила жизни (т.е. T либо имеет тривиальное уничтожение, либо программа не зависит от побочных эффектов деструктора), проблема в том, что ptr был настроен для этого неопределенного файла cookie, поэтому неверно передавать значение в operator delete[].

Ответ 5

После прочтения соответствующих стандартных разделов я считаю, что размещение new для типов массивов - это просто бесполезная идея, и единственная причина, по которой это допускается стандартом, - это общий способ описания нового оператора:

Новое выражение пытается создать объект typeid (8.1) или newtypeid, к которому он применяется. Тип этого объекта - это выделенный тип. Этот тип должен быть полным типом объекта, но не тип абстрактного класса или его массив (1,8, 3,9, 10,4). [Примечание: потому что ссылки не являются объектами, ссылки не могут быть созданы newexpressions. ] [Примечание: typeid может быть cvqualified type, in в этом случае объект, созданный новым выражением, имеет cvqualified тип. ]

new-expression: 
    ::(opt) new new-placement(opt) new-type-id new-initializer(opt)
    ::(opt) new new-placement(opt) ( type-id ) new-initializer(opt)

new-placement: ( expression-list )

newtypeid:
    type-specifier-seq new-declarator(opt)

new-declarator:
    ptr-operator new-declarator(opt)
    direct-new-declarator

direct-new-declarator:
    [ expression ]
    direct-new-declarator [ constant-expression ]

new-initializer: ( expression-list(opt) )

Мне кажется, что array placement new просто проистекает из компактности определения (все возможное использование как одна схема), и, похоже, нет веских оснований для его запрещения.

Это оставляет нас в ситуации, когда у нас есть бесполезный оператор, которому требуется память, прежде чем будет известно, сколько из этого потребуется. Единственные решения, которые я вижу, - это либо комбинировать память, либо надеяться, что компилятор не захочет больше, чем предоставил, или перераспределит память в переопределенном array placement new функции/методе (что скорее поражает цель использования array placement new в первую очередь).


Чтобы ответить на вопрос, заданный Керреком С.Б.: Ваш пример:

void * addr = std::malloc(N * sizeof(T));
T * arr = ::new (addr) T[N];                // #1

не всегда корректен. В большинстве реализаций arr!=addr (и для этого есть веские причины), поэтому ваш код недействителен, и ваш буфер будет переполнен.

Об этих "веских причинах" - обратите внимание, что вы освобождаетесь стандартными создателями от некоторого домоуправления при использовании оператора array new, а array placement new в этом отношении не отличается. Обратите внимание, что вам не нужно сообщать delete[] о длине массива, поэтому эта информация должна храниться в самом массиве. Где? Именно в этой дополнительной памяти. Без него delete[] 'ing потребовало бы, чтобы длина массива была отдельной (поскольку stl использует циклы и не размещение new)

Ответ 6

     Эти издержки могут применяться во всех новых выражениях массива, включая те, которые ссылаются на библиотечную функцию operator new[](std::size_t, void*) и другие функции размещения размещения.

Это дефект в стандарте. Ходят слухи, что они не смогли найти добровольца, чтобы написать исключение для него (Сообщение № 1173).

несменный массив размещения-новый нельзя использовать с выражениями delete[], поэтому вам нужно перебрать массив и вызвать каждый деструктор.

Накладные расходы предназначены для пользовательских функций размещения новых функций массива, которые выделяют память точно так же, как обычные T* tp = new T[length]. Они совместимы с delete[], поэтому накладные расходы несут длину массива.