Давайте рассмотрим следующий код. На самом деле это узкая проблема, которую я нашел, используя метод gmock и mocking void (void).
class Base {
public:
virtual ~Base() {}
};
class Derived : public Base
{
public:
void GetValueAndDelete() { delete this; } //here we crash
};
int main() {
Derived* p = 0;
p->GetValueAndDelete();
}
Строя это с:
/tools/gcc6.1/bin/g++ --version
g++ (GCC) 6.1.0
с уровнем оптимизации, отличным от -O0, и выполнение результата вызывает ошибку сегментации.
Это ошибка GCC или что-то с кодом C ++ (да, да, я знаю, что он использует побочные эффекты, но он работает с другими компиляторами и без оптимизации)
Это ошибка GCC
Нет.
или что-то с кодом C ++
Да. Вы используете оператор стрелки на указателе, который не указывает на допустимый объект. Это имеет неопределенное поведение.
Разыменование нулевого указателя и вызывающего метода таким способом, который не использует никаких членов, это хорошо.
Это не отлично в соответствии со стандартом. Это UB.
Что вызывает метод по указателю?
Это конкретная реализация.
Удаление это ничего особенного.
Удаление this
это очень особенное.
Вы должны позаботиться о том, чтобы не использовать какой-либо член после
Да это а также Вы должны убедиться, что только new
был использован для создания всех экземпляров, для которых функция когда-либо вызывалась. Нет автоматических объектов, нет статических объектов нет new[]
нет malloc
+ размещение нового.
Так что да, ты Можно delete this
, но будь осторожен.
Ваша интуиция здесь выглядит так:
p->
все в порядке, потому что это просто синтаксис для заполнения this
,delete this
в порядке, потому что вы не используете this
после delete
,Но вышеприведенное может не соответствовать действительности, потому что разыменование нулевого указателя — это всегда неопределенное поведение. Без включенной оптимизации это может не вызвать никаких проблем, потому что компилятор выполняет «базовую компиляцию», которая в некоторой степени соответствует тому, что вы представляете, когда «компилируете в своем уме». Однако с оптимизацией GCC сделает много вещей, которые он обычно не делал бы, например, не потрудившись выдавать «правильные» инструкции, когда сталкивался с неверным исходным кодом. Там буквально не нужно ничего делать, когда вы говорите p->
как первый акт вашей программы — он может просто притворяться main()
был пуст (или вылет, как в вашем случае).
Я знаю, что вызов метода для nullptr не соответствует стандарту, но до сих пор я не видел компилятора, который бы не справился с этим описанным способом. Кажется, что gcc6.1 работает по-другому (все еще согласно спецификации). Так что это ошибка Gmock. Интересно, сколько других проектов зависит от такого поведения компилятора 🙂
Стоит упомянуть, что метод вызывается, «this» внутри равно нулю, как и ожидалось, и затем удаление завершается неудачно. Я посмотрел на сборку и есть mov (% rax),% rbx, который не работает, потому что rax содержит ноль.