Я обнаружил еще один случай, когда компилятор считает, что static_cast
может вернуть nullptr для аргумента, отличного от nullptr. Вроде бы в этот раз нет неопределённого поведения, как было раньше, но может я что-то упускаю.
Вот код, скомпилированный с помощью gcc с параметрами -O1 -fPIC -Wnull-dereference -Werror
:
struct IR {
virtual ~IR() = default;
};
struct IF {
virtual ~IF() = default;
virtual void get() {};
};
struct SC : IR, IF {
bool h(); // { return true; };
};
struct HD : public IR {
virtual void g() {
if (r_ && r_->h()) {
static_cast<IF*>(r_)->get();
}
}
SC* r_ = nullptr;
};
bool SC::h() { return true; }
HD hd;
Любое из следующих условий отменяет предупреждение potential null pointer dereference
:
SC::h()
встроенным;IF::get()
не виртуальным;-O1
на -O0
;-fPIC
...Итак, есть ли ошибка GCC, или все еще UB в коде, или static_cast
для не-nullptr может привести к nullptr?
Ссылка на богболт .
@chris r_
как this
для h()
. Как это можно изменить в h()
?
Это может быть ссылка на hd
или hd.r_
. Очевидно, что мы можем сказать, что это не так, взглянув на полную программу, но это вся информация, которую необходимо внести g()
из внешнего анализа. С точки зрения g()
без посторонней помощи это возможно.
И, как и раньше, мы задаемся вопросом, для чего нужен static_cast? Почему бы не просто r_->get()
?
@BoP в реальном коде get
переопределяется и помещается в приватную часть SC
. Итак, кастинг на IF*
необходим для вызова get
.
В этом случае вы пытались r_->IF::get()
явно вызвать функцию базового класса?
@BoP Есть две причины: 1. используется более одного раза, поэтому явная переменная лучше. 2. это чисто виртуально в IF
.
Если вы используете -fPIC
и не используете функцию inline
для h
, то компилятор не может делать никаких предположений о поведении h
, потому что семантика GCC по умолчанию позволяет любой общей библиотеке переопределять функцию с альтернативной семантикой. Вы можете дополнительно указать -fno-semantic-interposition
, чтобы разрешить GCC оптимизацию в предположении, что другие реализации функции, загружаемой из общей библиотеки, не будут вести себя иначе.
Насколько я понимаю, -Wnull-dereference
не гарантирует предупреждение только в случае окончательного разыменования нулевого указателя, только в случае потенциального разыменования (хотя документация кажется немного неясной, см. Также https://gcc.gnu.org /bugzilla/show_bug.cgi?id=86172). Компилятор не может быть уверен, что r_->h()
не изменяет r_
по указанной выше причине, и поэтому проверка нулевого указателя не может доказать, что static_cast<IF*>(r_)->get();
не будет разыменовывать нулевой указатель.
Предупреждение не появляется для -O0
, потому что задокументировано, что предупреждение работает только при включенном -fdelete-null-pointer-checks
, то есть только при включенной оптимизации.
Я предполагаю, что предупреждение не появляется, если get
не является virtual
, либо преднамеренно, потому что компилятор может быть уверен, что вызов функции фактически не вызовет нулевое разыменование в практическом смысле, даже если указатель объекта является нулевым указателем (хотя это по-прежнему является UB по стандарту), или, альтернативно, функция встраивается до того, как выполняется проверка нулевого указателя. (Если функция virtual
, компилятор снова не может делать никаких предположений об этом вызове, поскольку вместо этого он может вызывать переопределение производного класса.)
Что вы думаете о возможности получить nullptr после static_cast
ing non-nullptr? Возможно ли это на С++?
@αλεχολυτ Это невозможно по закону. Либо static_cast
имеет определенное поведение и не приводит к нулевому указателю, либо имеет неопределенное поведение, и в этом случае нет смысла обсуждать возможные значения. Однако компилятор не мыслит стандартными правилами. Вероятно, он думает с точки зрения числовых значений адреса. В этом смысле static_cast
может привести к нулевому указателю, потому что он отрицательно смещает исходный указатель. Но это будет УБ.
Мне не кажется тривиальным доказать, что нулевого разыменования нет. Он должен знать из неместной информации, что
h()
никак не может изменитьсяr_
.