Как возможно, что доступ к nullptr работает?

У меня есть простой класс:

class B
{
public:
    int getData() { return 3; }
};

затем я инициализирую указатель на него с помощью nullptr:

B *foo{ nullptr };

И тут, пытаясь им воспользоваться, приходит сюрприз:

int t = foo->getData();

и t теперь равно 3. Как это возможно без построения класса? Это потому, что getData() не использует «это»? Это сломало все мои знания об указателях.

Это ожидаемое поведение? Я работаю в Visual Studio 2013.


person LeDYoM    schedule 01.03.2016    source источник
comment
Это работает, потому что foo->getData() эквивалентно getData(foo); и вызываемый объект никогда не разыменовывает аргумент. Обратите внимание, что хотя это и работает, в C++ оно классифицируется как неопределенное поведение.   -  person Nawaz    schedule 01.03.2016
comment
Спасибо всем за ответы. На мой взгляд, это не должно быть неопределенное поведение, оно должно давать сбой. Это вопрос безопасности с моей точки зрения. Если метод получает указатель на класс, и этот указатель имеет значение nullptr, я могу использовать этот указатель для вызова метода. Да, я знаю, что это не гарантирует, что это сработает, но это дыра в вашей библиотеке.   -  person LeDYoM    schedule 01.03.2016
comment
LeDYoM: На мой взгляд, это не должно быть неопределенное поведение, оно должно давать сбой. Пожалуйста, проявите терпение к языку, прочитайте цель разработки C++ и немного его истории.   -  person Nawaz    schedule 01.03.2016
comment
@Nawaz Я люблю C++ и понимаю, почему он работает. Но меня это удивило. Инварианты, которые могут понадобиться вашему классу, нарушаются этой функцией. Да, можно сказать, что нет инвариантов, если вы не используете этот указатель, но я разрабатываю систему, в которой я утверждал, что общий указатель класса не является nullptr, утверждение работает, но программа продолжалась (в отладке), и это работало, поэтому assert(class != nullptr) бесполезен. Но я предполагаю, что это поведение исходит из C. Я все еще люблю C++, давайте улучшим это в C++17;)   -  person LeDYoM    schedule 01.03.2016
comment
LeDYoM: На самом деле это не идея C++. Идея состоит в том, что компилятору разрешено предполагать, что такие вещи, как переполнение целого числа со знаком, никогда не происходит, а разыменованные указатели имеют допустимое значение. Если вы хотите получить сбой при разыменовании нулевого указателя, тогда ВЫ должны явно утверждать, что он действителен. Некоторые программы могут выполняться во много раз быстрее, если они не разветвляются по таким вещам без необходимости — в C++ идея состоит в том, что вы не платите за то, что не используете, а большинство программистов не хотят платить за это, поэтому удаление этого конкретного неопределенного поведения в С++ 17 маловероятно.   -  person Chris Beck    schedule 01.03.2016
comment
@Chris Beck Я полагаю, именно поэтому мы любим C++. Прочитав все ответы и комментарии, я сказал себе: черт возьми, в этом есть смысл. Но в первый раз, когда я это увидел (должен сказать, что думал об обвинении только Microsoft), я подумал, что это за дерьмо.   -  person LeDYoM    schedule 01.03.2016
comment
@LeDYoM: о, я только что перечитал твой комментарий. Так ты это утверждал. Да, assert странно работает в Windows, они думают, что вы можете продолжить после сбоя утверждения. В Linux и Mac это невозможно, а утверждение об ошибке всегда невозвратно. Что я делаю, так это не использую утверждение из <cassert>, вместо этого я пишу макрос ASSERT, который проверяет условие и вызывает std::abort в случае сбоя. Так как это работает одинаково на всех платформах. ИМО, пытающаяся продолжить после утверждения, является бессмысленным микрософтизмом, лучше иметь кросс-платформенное поведение. (Большинство людей просто живут с этим.)   -  person Chris Beck    schedule 01.03.2016
comment
@Chris Beck: Забудь об утверждении. Я использовал свой собственный ;) В любом случае, я знаю, что вы говорите правду. Но я как бы играл ;)   -  person LeDYoM    schedule 01.03.2016


Ответы (3)


Это ожидаемое поведение?

Нет, это UB, все возможно.

Это потому, что getData() не использует «это»?

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

person songyuanyao    schedule 01.03.2016
comment
Ожидаемое поведение не является взаимоисключающим с неопределенным поведением. Это именно то поведение, которое я ожидал. - person Benjamin Lindley; 01.03.2016

Это неопределенное поведение, так что вы действительно должны быть очень испуган.

Может случиться так, что вы что-то сделаете (в вашей реализации C++), потому что функция getData не является виртуальной и не использует ни одного члена B. Таким образом, сгенерированный код не разыменовывает нулевой указатель.

person Basile Starynkevitch    schedule 01.03.2016

Это неопределенное поведение, вы обращаетесь к методу-члену из nullptr. Никакое конкретное поведение или результат не будут определены.

Однако в этом случае; задано (cppreference):

Ключевое слово this — это выражение prvalue, значением которого является адрес объекта, для которого вызывается функция-член.

Поскольку значение NULL для this не разыменовывается - метод-член не является виртуальным и не обращается ни к каким данным-членам this - он "кажется" работать.

person Niall    schedule 01.03.2016
comment
Есть ли какие-либо известные вам условия, которые могут определить поведение, например. constexpr/inline/const/noexcept getData(), агрегатный/POD/пустой класс и т. д.? Любое ваше понимание будет оценено. - person John P; 12.10.2020
comment
Единственный, о котором я могу думать, это static, так как он избавляется от указателя this. - person Niall; 12.10.2020
comment
Думаю, я думаю о вещах, которые я видел на en.cppreference.com/w/cpp/language/expressions#Discarded-value_expressions и на связанных страницах о prvalues ​​и временной материализации. Но есть еще обсуждение здесь, чтобы поддержать то, что вы сказали. Лично я справляюсь с std::declval, когда мне нужен гипотетический объект, и, по сути, никогда не использую nullptr, но мне все равно может понадобиться их учитывать. Еще раз спасибо. - person John P; 13.10.2020