Новые места размещения и исключения

Оператор «размещение нового» объявляется так:

void* operator new (std::size_t size, void* ptr) noexcept;

Но хотя это не связано с каким-либо фактическим распределением, поэтому исключения с плохим распределением устраняются, все же возможно, что указатель указывает на неправильное местоположение, и в этом случае можно было бы ожидать получить диапазон или ошибку переполнения/опустошения, но не будет тот факт, что он был объявлен как noexcept, вместо этого просто прекратит выполнение?

Также означает ли это, что до размещения С++ 11 новый вызовет и попытается обработать std::unexpected в случае std::set_unexpected вместо прямого сбоя?

Не должна ли быть бросковая перегрузка размещения новых "на всякий случай"?


person Community    schedule 02.11.2014    source источник
comment
В случае чего? Вы несете ответственность за то, чтобы адрес был действительным, новое размещение по умолчанию ничего не делает, оно просто возвращает указанный вами адрес.   -  person user657267    schedule 02.11.2014
comment
Эта функция является неоперативной. Он даже не пытается писать в *ptr. Он просто вызывается новым выражением, которое всегда вызывает функцию распределения, и это функция отсутствия распределения. Построение объекта выполняется компилятором, оценивающим новое выражение (вне вызова этой функции).   -  person dyp    schedule 02.11.2014
comment
@dyp - большинство конструкторов обращаются к переменным-членам, иначе у вас будет объект с тривиальным, то есть без конструктора.   -  person    schedule 02.11.2014
comment
Как я уже сказал (и попытался уточнить при редактировании): конструктор не вызывается из этой operator new функции. Она вызывается компилятором после завершения этой функции как часть оценки нового выражения.   -  person dyp    schedule 02.11.2014
comment
@dyp - так вы имеете в виду, что размещение new практически ничего не делает, кроме как возвращает вам тот же указатель, который вы ему передали? Тогда какой смысл его вообще использовать? Накладные расходы?   -  person    schedule 02.11.2014
comment
Я думаю, что напишу это как ответ, но это займет некоторое время.   -  person dyp    schedule 02.11.2014
comment
Я не пробовал, но я ожидал, что если вы передадите неверный указатель на новое размещение, ваша программа, вероятно, выдаст segfault. Это единственное, что я могу разумно увидеть, и это не тот случай, который бросает. Я что-то пропустил?   -  person Tim Seguine    schedule 02.11.2014
comment
@TimSeguine - я предположил, что segfault можно предотвратить с помощью обработки исключений.   -  person    schedule 02.11.2014
comment
@user657267 user657267 нет, и я относительно уверен, что к тому времени, когда вы segfault, вы находитесь на территории неопределенного поведения, поэтому, кроме того, ваша кошка может забеременеть.   -  person Tim Seguine    schedule 02.11.2014
comment
Новое выражение вызывает функцию operator new, а затем вызывает конструктор. В новом случае размещения, который использует это operator new, operator new само по себе абсолютно ничего не делает и не может дать сбой, но новое выражение может по-прежнему иметь неопределенное поведение.   -  person aschepler    schedule 02.11.2014
comment
@TimSeguine неправильное имя пользователя :)   -  person user657267    schedule 03.11.2014
comment
@user657267 user657267 Наверное, я слишком доверяю автозаполнению. С 5k на этом сайте можно подумать, что вы уже выбрали новое имя пользователя. ;)   -  person Tim Seguine    schedule 03.11.2014


Ответы (4)


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

Ваша задача — указать правильный адрес для трудоустройства новичка.

ОТРЕДАКТИРОВАНО В ОТВЕТ НА КОММЕНТАРИИ: -

#include <new>        // Must #include this to use "placement new"
#include "Fred.h"     // Declaration of class Fred

    void someCode()
    {
      char memory[sizeof(Fred)];     // Line #1     //Allocate enough memory.
      void* place = memory;          // Line #2     // There's no need for this.

      Fred* f = new(place) Fred();   // Line #3 (see "NOTE" below)
      // The pointers f and place will be equal

      ...
    }

ПРИМЕЧАНИЕ. Вы берете на себя единоличную ответственность за то, чтобы указатель, который вы передаете оператору «размещение нового», указывал на область памяти, которая достаточно велика и правильно выровнена для типа объекта, который вы создаете. Ни компилятор, ни система времени выполнения не пытаются проверить, правильно ли вы это сделали. Если ваш класс Fred должен быть выровнен по 4-байтовой границе, но вы указали местоположение, которое не выровнено должным образом, вы можете столкнуться с серьезной катастрофой.

Короче говоря, это означает, что вы должны быть осторожны с использованием размещения new ИЛИ, если вы такой парень, как я, никогда не используйте его :)

Надеюсь, это рассеет ваши сомнения.

person ravi    schedule 02.11.2014
comment
Я хочу сказать, что запись в void * по определению не может считаться безотказной операцией. - person ; 02.11.2014
comment
Если вас беспокоят нарушения прав доступа и т. д. Это вещи, которых нет в стандарте. - person defube; 02.11.2014
comment
Это ваша работа - предоставить правильный адрес для размещения новых на работе, можно утверждать, что ваша работа - писать правильные программы без ошибок, так зачем вам вообще нужна обработка ошибок :) Вот почему они называют это исключениями - person ; 02.11.2014
comment
Я хочу сказать, что не всегда будет так просто, как выделить место и перейти к размещению нового - этот указатель может оказаться продуктом очень сложной логики, которая управляет кучей пулов памяти с большим количеством кода для получения ошибок. Программирование не сводится к тривиальным примерам. - person ; 02.11.2014
comment
можно утверждать, что ваша работа - писать правильные программы без ошибок, да, это правильно/хорошо, но не обязательно, как при использовании нового размещения. - person ravi; 02.11.2014

Чтобы понять, что делает эта функция, я думаю, необходимо взглянуть на то, что делает new-expression: она вызывает функцию распределения, чтобы получить хранилище для объекта, затем создает этот объект в область памяти, указанную функцией выделения (путем возврата указателя на указанную область памяти).

Это означает, что конструкция никогда не выполняется самой функцией распределения. Функция распределения имеет странное имя operator new.

Можно указать дополнительные параметры для функции распределения, используя синтаксис размещения-нового:

new int(5)        // non-placement form
new(1,2,3) int(5) // placement-form

Однако placement-new обычно относится к очень конкретному new-expression:

void* address = ...;
::new(address) int(5) // "the" placement-form

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

Для этого случая в базовом языке не было введено специального случая. Скорее, в Стандартную библиотеку была добавлена ​​специальная функция распределения:

void* operator new (std::size_t size, void* ptr) noexcept;

Будучи неоперативным (return ptr;), он позволяет явно вызывать конструктор для объекта, создавая его в заданном месте памяти. Этот вызов функции может быть устранен компилятором, поэтому накладные расходы не вводятся.

person dyp    schedule 02.11.2014
comment
Так что в основном это просто ради синтаксического сахара, чтобы сделать вызов конструкторов по произвольному адресу памяти более понятным. По крайней мере, чистый в соответствии с низкими стандартными стандартами комитета... - person ; 02.11.2014

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

Вы можете написать свой собственный, который проверяет его ввод.

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

person defube    schedule 02.11.2014
comment
Я согласен, что вероятность случайного предоставления неверного указателя меньше, чем нехватка памяти, но, тем не менее, она существует. Конечно, вы можете проверить, но даже это может привести к сбою в больших и сложных сценариях. - person ; 02.11.2014
comment
Что ж, если под сложным вы подразумеваете, что что-то недобросовестно захватывает указатели конструктора и вызывает их напрямую, то, безусловно, да. Мой опыт был таким же, как указано выше: я использую его для обнаружения махинаций с выравниванием, даю себе возможность отслеживать экземпляры, созданные контейнерами, и т. д. - person defube; 02.11.2014
comment
Я имею в виду, даже без намерения, плохие вещи случаются. В этом весь смысл исключений, поэтому мне кажется странным оставлять такой крайний случай, как запись в void *, без возможности обработки возможных исключений. Не похоже, что это какая-то операция, которая гарантированно не подведет... - person ; 02.11.2014
comment
Типичное неотладочное размещение new просто возвращает свой аргумент. Если конструктор вызывает исключение, соответствующее удаление места размещения вызывается с тем же аргументом (который обычно не является операцией). - person defube; 02.11.2014

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

Оно не может. В языковом стандарте нет ничего, что позволяло бы это. Конечно, проверка на случай nullptr возможна, но лишь незначительно.

Ошибки выравнивания строго UB, поэтому любая реализация вольна делать что угодно. (даже просто заставить его работать: x86)

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

person Martin Ba    schedule 02.11.2014
comment
Нет, я просто неправильно предположил, что программа может восстановиться после segfault, обработав какое-то исключение. - person ; 02.11.2014
comment
@ user3735658 - думаю, это зависит от того, где работает ваш код. MSVC с /EHa ... просто выполните catch(...), и вы поймаете свой segfault. Просто не очень хорошая идея. - person Martin Ba; 02.11.2014
comment
Все же предпочтительнее загадочного крушения. - person ; 02.11.2014
comment
@user3735658 user3735658 — Сбои обычно не являются тайной, так как правильно настроенная система создаст дамп ядра / дамп WER, указывающий вам точно на то место, где произошел сбой. /EHa будет ловить ошибки до тех пор, пока состояние вашей программы не станет настолько запутанным, что, когда вы, наконец, выйдете из строя, вы ничего не сможете сделать из дампа памяти. - person Martin Ba; 02.11.2014