Массив размещения-новый требует неопределенных накладных расходов в буфере? - PullRequest
61 голосов
/ 04 января 2012

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

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

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

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

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, то предварительно выделенный буфер будет слишком маленьким!

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

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

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

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

Ответы [ 6 ]

42 голосов
/ 04 января 2012

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

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

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 составляет 1 слово.Размер слова зависит от того, собираюсь ли я для 32-битной или 64-битной архитектуры.

8 голосов
/ 04 января 2012

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

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

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

Всегда ли правильна следующая конструкция? 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, достаточно большой или как мы узнали бы, сколько памяти выделить.


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

  1. Ваш пример звонит operator new[](), а не operator new().

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

В результате выражение T * p = new T[10]; вызывает:

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

  2. десять вызовов к конструктору по умолчанию 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.

6 голосов
/ 04 января 2012

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

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

5 голосов
/ 10 марта 2016

Как упомянуто Kerrek SB в комментариях, об этом дефекте впервые было сообщено в 2004 году , и оно было решено в 2012 году как:

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

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

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

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


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

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

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

1 голос
/ 05 января 2012

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

Новое выражение пытается создать объект typeid (8.1) или newtypeid, к которому он применяется. Тип этого объекта выделенный тип. Этот тип должен быть полным типом объекта, но не абстрактный тип класса или его массив (1.8, 3.9, 10.4). [Примечание: потому что ссылки не являются объектами, ссылки не могут быть созданы newexpressions. ] [Примечание: typeid может быть cvqualified типом, в В этом случае объект, созданный выражением newexpression, имеет 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[] 'потребует разделения длины массива (как в stl с использованием циклов и без размещения new)

0 голосов
/ 26 апреля 2016

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

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

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

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

...