Законно ли вызывать удаление для нулевого указателя неполного типа?

И если да, то почему следующий код выдает предупреждение

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

?

struct C;

int main()
{
    C *c = nullptr;

    delete c;

    return 0;
}

Я понимаю, почему это может быть неопределенным поведением в общем случае, если C имеет нетривиальные / виртуальные деструкторы, но стандартная гарантия / не определяет, что delete на nullptr является всегда noop независимо от ситуации?

Повторюсь: я конкретно спрашиваю о случае, когда указатель на неполный тип равен nullptr!


person Dan M.    schedule 17.11.2017    source источник
comment
Тот факт, что компилятор выдает предупреждение, не обязательно означает, что код неправильный. Например, неиспользуемая переменная. Обычно это означает, что код небезопасен.   -  person Slava    schedule 17.11.2017
comment
Для меня вопрос не имеет особого смысла. Либо вы планируете, что c всегда будет NULL, тогда вам вообще не нужна строка с удалением, либо вы в конечном итоге разрешите присвоить ей какое-то значение позже, и в этом случае вам нужно полное определение типа ...   -  person lukas    schedule 17.11.2017
comment
@Fang, мой вопрос касается неполных типов.   -  person Dan M.    schedule 17.11.2017
comment
@ Слава ну правда. Вот почему я решил спросить, чтобы убедиться. По крайней мере, этот случай кажется достаточно простым для компиляторов, чтобы понять и не выдавать предупреждение (но, опять же, они могли бы просто не беспокоиться о реализации такого углового случая).   -  person Dan M.    schedule 17.11.2017
comment
Ну, либо C *c = nullptr; delete c; в любом случае не должно быть, потому что он ничего не делает, и компилятор действительно сможет это обнаружить и оптимизировать. Но такой код указывает на то, что с вашим кодом что-то не так, реализовывать этот частный случай в компиляторе не стоит.   -  person t.niese    schedule 17.11.2017
comment
Разве проверка времени выполнения не требуется, чтобы определить, является ли указатель нулевым? Значит, это чек, за которым следует грохот?   -  person wally    schedule 17.11.2017
comment
@lukas, вероятно, следует добавить тег language-lower, но в любом случае это законный вопрос, поскольку в этом случае стандарт может быть неоднозначным. Ноуп с одной стороны, УБ с другой.   -  person Slava    schedule 17.11.2017
comment
@Bo Persson, я четко указал в своем вопросе, почему это не дубликат, пожалуйста, посмотрите последний абзац и удалите повторяющийся знак. Я читал много похожих вопросов SO (даже с почти одним и тем же фрагментом кода), но все они были о разных вещах и не ответили на мой вопрос.   -  person Dan M.    schedule 17.11.2017
comment
Почему не delete nullptr; напрямую?   -  person iBug    schedule 17.11.2017
comment
@ t.niese это самый простой пример. Тот же случай может (и, вероятно, будет) возникать, например, в каких-то интеллектуальных указателях, где вы знаете, что деструктор может быть вызван только в том случае, если сохраненный ptr имеет значение null или тип завершен.   -  person Dan M.    schedule 17.11.2017
comment
@iBug, потому что nullptr имеет другой тип   -  person Slava    schedule 17.11.2017
comment
@iBug есть различия между типами и значениями. В delete c тип C со значением nullptr. В delete nullptr значение nullptr с типом nullptr_t.   -  person Severin Pappadeux    schedule 17.11.2017
comment
Я поменял один из ваших тегов: я считаю, что это больше вопрос юриста, чем вопрос UB.   -  person Bathsheba    schedule 17.11.2017


Ответы (1)


В стандарте сказано ([expr.delete] / 5):

Если удаляемый объект имеет неполный тип класса на момент удаления, а полный класс имеет нетривиальный деструктор или функцию освобождения, поведение не определено.

Итак, если T имеет нетривиальный деструктор или имеет operator delete перегрузку, вы получите UB. Ничего не сказано о том, что UB основан на значении указателя (то есть, является ли он нулевым указателем или нет).


Что значит "объект удаляется"?

Можно подумать, что «объект удаляется» означает, что это предложение применяется только к delete вызовам реальных объектов. И поэтому, если вы передадите нулевой указатель, он не применяется.

Во-первых, остальная часть стандартного обсуждения поведения delete явно указывает на то, что его поведение не применяется к нулевым указателям. [expr.delete] / 6 и 7 оба начинаются с «Если значение операнда выражения удаления не является значением нулевого указателя». Пункт 5 явно не содержит этих слов. Следовательно, мы должны предположить, что это применимо к нулевым указателям.

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

Хорошо, подумайте, что значит интерпретировать этот текст, если «объект удаляется» конкретно говорит об объекте в конце этого указателя. Что произойдет, если вы удалите массив неполных классов с помощью нетривиальных деструкторов?

По этой логике это предложение не применяется вне зависимости от того, является ли указатель нулевым или нет. Почему? Поскольку «удаляемый объект» относится к типу массив, а не к типу класса. Следовательно, этот пункт не может применяться. Это означает, что компилятор должен иметь возможность вызывать delete[] для массива неполных классов.

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

Таким образом, либо «удаляемый объект» предназначен для ссылки на std::remove_pointer_t<std::decay_t<decltype(expr)>>, либо стандарт требует поведения, которое невозможно реализовать. Стандартную формулировку, вероятно, можно было бы немного очистить, заменив «Если удаляемый объект имеет неполный тип класса в момент удаления» на «Если T является указателем на U или массив U, а U имеет неполный тип класса в точка удаления, ... "

person Nicol Bolas    schedule 17.11.2017
comment
Но если значение указателя равно нулю, значит, объекта нет. Так что это не применимо. - person wally; 17.11.2017
comment
Вы тоже должны были разместить ссылку на источник - person Asesh; 17.11.2017
comment
@rex: есть ли у него неполный тип - это вопрос времени компиляции. Если бы стандарт хотел принять во внимание, является ли указатель нулевым, он бы сказал об этом, как это всегда делается для всего поведения после этого оператора. - person Nicol Bolas; 17.11.2017
comment
@rex Это правда ясно? Я имею в виду, что если есть перегруженный оператор delete, не указано, вызывается ли он с помощью nullptr. Это звучит довольно туманно, но, по общему признанию, только с точки зрения языкового законодательства. - person Tim Seguine; 17.11.2017
comment
@rex Многие вещи, которые стандарт объявляет как UB, с технической точки зрения могут показаться однозначно определенным поведением. ((Foo*)nullptr)->someStaticVariable, например. Ни один компилятор в здравом уме на самом деле не выдаст здесь код для разыменования нулевого указателя. Тем не менее, заявление UB. И тот факт, что он объявлен как UB, означает, что вы должны ожидать, что какой-то (будущий) компилятор просто выбросит ваш вызывающий код UB в качестве оптимизации. - person cmaster - reinstate monica; 17.11.2017
comment
@rex: Я считаю, что это не поведение undefined, если удаление вызывается для нулевого указателя и оператор удаления не перегружен. Но это не поддерживается стандартом. - person Nicol Bolas; 17.11.2017
comment
Если время жизни объекта не началось, это объект? Я могу ошибаться здесь, и я вижу, что остальная часть стандарта действительно вызывает значение нулевого указателя в качестве определенного квалификатора в остальных операторах. В конце концов, кажется, что определяющим фактором для UB является не значение указателя, а то, имеет ли оператор удаления какое-либо отношение. - person wally; 17.11.2017
comment
К чему относится удаляемый объект? Операнд самого оператора delete или объект, на который указывает операнд оператора delete? (Подлинный вопрос, у меня нет стандарта.) Если первое, этот ответ имеет смысл. Если последнее, с операндом нулевым указателем, удаляемый объект не имеет референта, как текущий король Франции, и оператор просто неприменим. Было бы предусмотрено, что нулевой указатель не имеет значения в этом случае; неявно, это делает, потому что нулевой указатель никогда не указывает на объект. - person Jeroen Mostert; 17.11.2017
comment
@rex: А что, если указатель является указателем на массив классов с нетривиальными деструкторами? По вашему мнению, это предложение не будет применяться, поскольку удаляемый объект имеет тип массива, а не тип класса. И поэтому компилятор должен должен заставить его работать. Однако, если класс неполный, компилятор не может заставить его работать. Так что либо стандарт противоречит сам себе, либо ваша интерпретация неверна. - person Nicol Bolas; 17.11.2017
comment
@Nicol but [expr.delete.3] читает Во второй альтернативе (массив удаления), если динамический тип удаляемого объекта отличается от его статического типа, поведение не определено, это означает, что удаляемый объект относится к первому (или, возможно, любому) элементу массива, а не к самому массиву (в противном случае приведенное выше утверждение не имело бы смысла). - person Massimiliano Janes; 17.11.2017
comment
@MassimilianoJanes: В этом еще меньше смысла. Удаляемый объект представляет собой массив; его динамический тип и статический тип одинаковы. Тип массива удаляет несколько подобъектов массива, поэтому он не может ссылаться на конкретный подобъект массива. - person Nicol Bolas; 17.11.2017
comment
@Nicol, если бы удаляемый объект был массивом, и учитывая, что его динамический и статический типы не могут различаться, [expr.delete.3] заявит об очевидном ... имеет ли это смысл? думаю, нет - person Massimiliano Janes; 17.11.2017
comment
Итак, на каком-то уровне ... вы должны хотя бы попытаться понять, в чем смысл этого предложения. И цель этого пункта - безусловно иметь дело с вопросом о неполных типах. Эта ветка ... что-то еще. - person Barry; 17.11.2017
comment
Помните, всегда есть вариант номер 3: в стандарте есть дефект, и его буквальные слова не точно передают его намерение. Точно не в первый раз. Я утверждаю, что это происходит, как только разумные люди могут начать убедительно спорить о том, какая интерпретация лучше. :-П - person Jeroen Mostert; 17.11.2017