Программа может не аварийно завершить работу, когда программа разыменовывает нулевой указатель?

Раньше я считал, что программа сразу же упадет при разыменовании нулевого указателя.

Но фрагмент кода ниже работает хорошо. Какой сюрприз!

#include <iostream>
#include <vector>

struct foo
{
    void test()
    {
        std::cout << "This is displayed\n";
    }
};

int main()
{
    foo *f = nullptr;
    f->test();
}

Неопределенное поведение по сути означает «все может случиться». С этой точки зрения вы больше не сможете рассуждать. Единственный способ узнать, что на самом деле происходит (т. е. что сделал компилятор), — это посмотреть на сгенерированный ассемблерный код.

Fareanor 29.05.2024 11:28

Распространенное заблуждение, но это была бы функция безопасности, которой нет в C++ по умолчанию.

Cubic 29.05.2024 11:35

В C++ компилируемый код не обязательно является правильной программой и не обязательно работает (правильно). Разыменование nullptr не разрешено C++ (виртуальная машина C++), и C++ может генерировать любой код, который захочет. (проблемы обычно проявляются, когда вы выполняете оптимизированную сборку, хотя ваш пример, похоже, тоже является исключением: godbolt.org/z/ebPqbKere). Вы можете видеть, что весь вызов функции оптимизирован, поскольку «это» не требуется.

Pepijn Kramer 29.05.2024 11:36

Когда вы переходите красный свет светофора, вас не сразу сбивает машина. То же самое

463035818_is_not_an_ai 29.05.2024 11:53

Пожалуйста, проведите надлежащее исследование, прежде чем задавать какой-либо вопрос, как рекомендовано кнопкой «понижение» в вопросе сообщения и другими рекомендациями SO.

user12002570 29.05.2024 12:16

См. en.cppreference.com/w/cpp/language/ub

Jesper Juhl 29.05.2024 12:48

Раньше я считал, что программа сразу же упадет при разыменовании нулевого указателя. Это не гарантия C++. C++ не определяет, что происходит при разыменовании нулевого указателя. Вот почему разыменование нулевого указателя — это классическое неопределенное поведение. Сбой из-за этого происходит из-за вашей платформы. Не все платформы потерпят крах из-за этого. (Ваш код дает сбой на моей машине.)

Eljay 29.05.2024 17:20
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
8
91
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Разыменование nullptr — это неопределенное поведение. Таким образом, он может выйти из строя или нет. Компилятор может просто пропустить/оптимизировать строку с помощью этой операции, поскольку со стороны пользователя гарантируется, что nullptr никогда не будет разыменовано.

Почему компилятор предпочитает игнорировать процесс разыменования? Просто потому, что для структуры нет переменных-членов?

John 29.05.2024 11:30

Пытаться объяснить, почему компилятор делает что-то, если у вас неопределенное поведение, не имеет смысла.

Pepijn Kramer 29.05.2024 11:42

@John Все оптимизации основаны на некоторых предположениях. Например, вам нужно предположить, что вектор отсортирован для выполнения двоичного поиска. Итак, давайте возьмем эти предположения куда-нибудь. Давайте договоримся, что пользователи будут следовать некоторым правилам и делать оттуда предположения. Давайте договоримся, что пользователь никогда не будет разыменовывать nullptr. Тогда, если все пространство возможных путей может пройти выполнение, мы можем предположить, что пути, ведущие к *nullptr, невозможны (пользователь гарантирует это). Итак, давайте удалим их из кода. Отсутствие переменных-членов не имеет к этому никакого отношения. Речь идет о том, как работает оптимизация в целом.

Bohdan Lakatosh 29.05.2024 11:44

Другими словами, компилятор предполагает, что он знает, что вы делаете (чтобы сделать его жизнь намного проще);)

Pepijn Kramer 29.05.2024 11:45

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

Peter 29.05.2024 14:01
Ответ принят как подходящий

Ваш код на самом деле не смотрит на указатель this, который передается в качестве скрытого первого аргумента функции-члена. Вот как оно выжило. Если вы добавите что-то, что будет использовать this, например, создадите функцию virtual, произойдет сбой, как вы и ожидали.

https://godbolt.org/z/YfxcdqdPj

Конечно, неопределенное поведение не определено, в этом случае компилятору разрешено делать абсолютно все, поэтому логические рассуждения не помогут вам.

Если вы включите оптимизацию, компилятор также может просто вставить туда инструкцию ud2 (явный сбой).

https://godbolt.org/z/YWfMefYoG

В вашем первом пункте ".... произойдет сбой, как вы и ожидали". это неверно. Более точным утверждением было бы: «...он может выйти из строя, как вы и ожидали». На практике, безусловно, можно найти примеры кода, подобного тому, что вы описываете, который НЕ ВЫЗЫВАЕТ сбой при использовании некоторых или всех компиляторов (или параметров сборки), которые вы использовали.

Peter 29.05.2024 13:33

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