Как видно из названия:
Почему вызов не виртуальной функции-члена для удаленного указателя является неопределенным поведением?
Обратите внимание, что Вопрос не спрашивает, является ли это неопределенным поведением, он спрашивает Зачем это неопределенное поведение.
Рассмотрим следующая программа:
#include<iostream>
class Myclass
{
//int i
public:
void doSomething()
{
std::cout<<"Inside doSomething";
//i = 10;
}
};
int main()
{
Myclass *ptr = new Myclass;
delete ptr;
ptr->doSomething();
return 0;
}
В приведенном выше коде компилятор фактически не разыменовывает this
при вызове функции-члена doSomething()
, Обратите внимание, что функция не является виртуальной функцией & компиляторы преобразуют вызов функции-члена в обычный вызов функции, передавая это в качестве первого параметра функции (насколько я понимаю, это определяется реализацией). Они могут сделать это, потому что компилятор может точно определить, какую функцию вызывать во время компиляции. Практически, вызов функции-члена через удаленный указатель не разыменовывает this
, this
разыменовывается, только если доступ к какому-либо члену внутри тела функции. (т. е. раскомментированный код в вышеприведенном примере, который обращается к i
)
Если член не доступен внутри функции, нет никакой цели, что приведенный выше код должен фактически вызывать неопределенное поведение.
Так почему же стандартный мандат, согласно которому вызов не виртуальной функции-члена через удаленный указатель, является неопределенным поведением, хотя на самом деле он может надежно сказать, что разыменование this
должно быть утверждение, которое должно вызывать неопределенное поведение? Это просто для простоты для пользователей языка, что стандарт просто обобщает его, или есть некоторая более глубокая семантика, вовлеченная в этот мандат?
Мне кажется, что, возможно, поскольку именно реализация определяется тем, как компиляторы могут вызывать функцию-член, это может быть причиной того, что стандарт не может обеспечить фактическую точку, в которой возникает UB.
Может кто-нибудь подтвердить?
[expr.ref] параграф 2 говорит, что вызов функции-члена, такой какТак почему же стандартный мандат, согласно которому вызов не виртуальной функции-члена через удаленный указатель, является неопределенным поведением, когда на самом деле он может надежно сказать, что разыменование this должно быть оператором, который должен вызывать неопределенное поведение?
ptr->doSomething()
эквивалентно (*ptr).doSomething()
так вызывая нестатическую функцию-член является разыменование. Если указатель недействителен, это неопределенное поведение.
Нужно ли сгенерированному коду разыменовывать указатель для конкретных случаев, не имеет значения, абстрактная машина, которую моделирует компилятор делает сделать разыменование в принципе.
Сложность языка для точного определения того, какие случаи будут разрешены, если они не имеют доступа ни к каким членам, будут иметь практически нулевую выгоду. В случае, когда вы не можете увидеть определение функции, вы не представляете, будет ли ее вызов безопасным, потому что вы не можете знать, использует ли функция this
или нет.
Только не делайте этого, нет веских причин для этого, и это хорошо, что язык запрещает это.
Потому что число случаев, в которых это может быть надежно, очень мало, и делать это по-прежнему невыразимо глупо. Нет смысла определять поведение.
В языке C ++ (согласно C ++ 03) сама попытка использование значение недопустимого указателя уже вызывает неопределенное поведение. Нет необходимости разыменовывать его для UB. Достаточно просто прочитать значение указателя. Понятие «недопустимое значение», которое вызывает UB, когда вы просто пытаетесь прочитать это значение, на самом деле распространяется почти на все скалярные типы, а не только на указатели.
После delete
указатель обычно недопустим в этом конкретном смысле, то есть чтение указателя, который предположительно указывает на то, что только что было «удалено», приводит к неопределенному поведению.
int *p = new int();
delete p;
int *p1 = p; // <- undefined behavior
Вызов функции-члена через недопустимый указатель является лишь частным случаем вышеупомянутого. Указатель используется в качестве аргумента для неявного параметра this
, Передача указателя является нерегулярным аргументом и является актом его чтения, поэтому поведение в вашем примере не определено.
Итак, ваш вопрос действительно сводится к тому, почему чтение неверных значений указателя вызывает неопределенное поведение.
Ну, может быть много причин для конкретной платформы. Например, на некоторых платформах действие чтения указателя может привести к загрузке значения указателя в какой-то специальный регистр, специфичный для адреса. Если указатель недействителен, аппаратное обеспечение / ОС может немедленно обнаружить его и вызвать программную ошибку. Фактически, так работает наша популярная платформа x86 в отношении сегментных регистров. Единственная причина, по которой мы мало что слышим об этом, заключается в том, что популярные ОС придерживаются модели плоской памяти, которая просто не использует активно регистры сегментов.
C ++ 11 фактически утверждает, что разыменовании неверные значения указателя вызывают не определено поведение, в то время как все другие использования недопустимого значения указателя вызывают от реализации поведение. Он также отмечает, что поведение, определяемое реализацией в случае «копирования недопустимого указателя», может привести к «системной ошибке времени выполнения». Таким образом, на самом деле можно было бы осторожно маневрировать через лабиринт спецификации C ++ 11 и успешно прийти к выводу, что вызов не виртуального метода через недопустимый указатель должен привести к от реализации поведение, упомянутое выше. В любом случае, всегда существует вероятность «системной ошибки во время выполнения».
Разыменование this
в данном случае это фактически деталь реализации. Я не говорю, что this
Указатель не определен стандартом, потому что он есть, но с семантически абстрагированной точки зрения, какова цель разрешения использования уничтоженных объектов, просто потому, что существует угловой случай, в котором на практике он будет «безопасным» ? Никто. Так что нет. Никакого объекта не существует, поэтому вы не можете вызывать на нем функцию.