Оба примера имеют неопределенное поведение. Теперь, когда я потратил время на просмотр стандартного окончательного варианта C ++ 17 , я нашел необходимые мне доказательства.
Пример A
Относительно operator new
:
Функции распределения - § 6.7.4.1.2
Если запрос выполнен успешно, возвращается значение должно быть ненулевым значением указателя (7.11) p0, отличным от любого ранее возвращенного значения p1, если только это значение p1 не было впоследствии передано operator delete
В примере A мы вызываем new-expression , Simple* a = new Simple()
, которое внутренне будет вызывать соответствующий operator new
. Мы обходим operator delete
, когда мы звоним UserImplementedFree(static_cast<void*>(a))
. Даже если operator delete
вызовет эту функцию и, вероятно, сделает то же самое освобождение, подвох в том, что любые последующие вызовы operator new
теперь могут потенциально возвращать указатель, который соответствует адресу, который имел a
. И a
никогда не передавался operator delete
. Итак, мы нарушили указанное выше правило.
Пример B
delete-expression - § 8.3.5.2
... значение операнда удаления может быть значением нулевого указателя, указателем на объект, не являющийся массивом, созданным предыдущим новым выражением, или указателем на подобъект (4.5), представляющий базовый класс такой объект (пункт 13). Если нет, поведение не определено. Во второй альтернативе (массив удаления) значением операнда удаления может быть нулевое значение указателя или значение указателя, которое возникло из предыдущего массива new-expression. 83 Если нет, поведение не определено.
В примере B мы не выделяем addr
через new-expression . И затем мы пытаемся использовать delete-expression для его освобождения. Что нарушает вышеприведенное правило.
Как бы выглядело определенное поведение?
Основной особенностью этих примеров является отделение конструкции от распределения и отделение разрушения от освобождения. Стандарт гласит следующее:
new-expression - § 8.3.4.11
Для массивов char
, unsigned char
и std::byte
разница между результатом выражения new-expression и адресом, возвращаемым функцией выделения, должна быть целым кратным самого строгого требования фундаментального выравнивания (6.11) любого типа объекта, размер которого не превышает размер создаваемого массива. [Примечание: поскольку предполагается, что функции выделения возвращают указатели на хранилище, которые соответствующим образом выровнены для объектов любого типа с фундаментальным выравниванием, это ограничение на накладные расходы на выделение массивов допускает общую идиому распределения массивов символов, в которую будут поступать объекты других типов позже будет размещен . - конец примечания]
Таким образом, определенное поведение потенциально может выглядеть так:
{
//Allocates bytes
char* bytes = new char[sizeof(Simple)];
//Invokes constructor
Simple* a = new ((void *)bytes) Simple();
//Invokes destructor
a->~Simple();
//Deallocates
delete[] bytes;
}
Еще раз, не обязательно хорошая практика, но определенное поведение.