Потенциальное разыменование нулевого указателя или ошибка компилятора?

Я обнаружил еще один случай, когда компилятор считает, что 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?

Ссылка на богболт .

Мне не кажется тривиальным доказать, что нулевого разыменования нет. Он должен знать из неместной информации, что h() никак не может измениться r_.

chris 25.12.2022 23:23

@chris r_ как this для h(). Как это можно изменить в h()?

αλεχολυτ 25.12.2022 23:26

Это может быть ссылка на hd или hd.r_. Очевидно, что мы можем сказать, что это не так, взглянув на полную программу, но это вся информация, которую необходимо внести g() из внешнего анализа. С точки зрения g() без посторонней помощи это возможно.

chris 25.12.2022 23:32

И, как и раньше, мы задаемся вопросом, для чего нужен static_cast? Почему бы не просто r_->get()?

BoP 26.12.2022 00:03

@BoP в реальном коде get переопределяется и помещается в приватную часть SC. Итак, кастинг на IF* необходим для вызова get.

αλεχολυτ 26.12.2022 09:24

В этом случае вы пытались r_->IF::get()явно вызвать функцию базового класса?

BoP 26.12.2022 09:49

@BoP Есть две причины: 1. используется более одного раза, поэтому явная переменная лучше. 2. это чисто виртуально в IF.

αλεχολυτ 26.12.2022 12:13
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
7
120
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Если вы используете -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_casting non-nullptr? Возможно ли это на С++?

αλεχολυτ 26.12.2022 09:27

@αλεχολυτ Это невозможно по закону. Либо static_cast имеет определенное поведение и не приводит к нулевому указателю, либо имеет неопределенное поведение, и в этом случае нет смысла обсуждать возможные значения. Однако компилятор не мыслит стандартными правилами. Вероятно, он думает с точки зрения числовых значений адреса. В этом смысле static_cast может привести к нулевому указателю, потому что он отрицательно смещает исходный указатель. Но это будет УБ.

user17732522 26.12.2022 13:15

Другие вопросы по теме