Почему я могу вызвать функцию IsValidLowLevel() указателя без проверки на ноль?

Хорошая практика в UE5 — убедиться, что вы не вызываете функцию с nullptr.

Одним из способов сделать это будет вызов функции указателей IsValidLowLevel() следующим образом:

if (ActorPtr->IsValidLowLevel())
{
    ActorPtr->DoSomething();
}

Но почему этот указатель актера имеет гарантированный доступ к этой функции, если он действительно имеет значение nullptr? Не должна ли программа аварийно завершиться, если вы вызываете функцию с нулевым значением nullptr так же, как если бы я вызывал функцию DoSomething() ActorPtr? И есть ли разница в том, как эти функции называются?

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

Я просмотрел документацию UE5 по этой функции, но она очень полезна и говорит, что она выполняет нулевую проверку, и не вдается в подробности, как она это делает.

Раньше я делал что-то подобное в одном из своих API, но мне пришлось это изменить. Проблема в том, что компилятор может предположить, что всякий раз, когда вы вызываете x->y(), это x всегда будет не NULL, поэтому любые проверки на достоверность указатель this, который вы, возможно, захотите сделать внутри y(), может быть и, скорее всего, будет оптимизирован за ненадобностью. Единственный способ обеспечить надежную работу такого рода тестов — сделать их статическим методом или отдельной функцией (например, bool IsValidLowLevel(const Actor * actorPtr) {return (actorPtr != NULL);}).

Jeremy Friesner 06.06.2024 19:45

Я бы сказал, что if (ActorPtr->IsValidLowLevel()), наверное, должно быть if (ActorPtr && ActorPtr->IsValidLowLevel()).

Jesper Juhl 06.06.2024 20:26

Хотя многие люди скажут вам, что вызов метода для нулевого объекта — это UB (и они правы), на практике компиляторы могут генерировать разумный, не вызывающий сбоев код, который ведет себя предсказуемо для формального UB. если ActorPtr на самом деле указывает на действительную память, которую вы можете прочитать, но НЕ на UObject, тогда сгенерированный код обращается только к тривиальному члену с фиксированным смещением относительно объекта (индекс объекта), и крайне маловероятно, что объект в глобальном таблица по этому индексу является переданным объектом. Вот почему метод говорит: «Верните true, если это действительный объект».

user2407038 06.06.2024 20:54

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

user2407038 06.06.2024 20:57

Чтобы охватить некоторые из этих ответов! IsValidLowLevel Не происходит сбой, если указатель действительно равен нулю, поэтому этот вопрос вообще важен. и if (ActorPtr->IsValidLowLevel()) идентично выполнению if (ActorPtr && ActorPtr->IsValidLowLevel()), достигают того же самого, поскольку IsValidLowLevel() используется для проверки нуля

Wardy 07.06.2024 22:09

@Wardy «IsValidLowLevel не дает сбоя, если указатель действительно равен нулю» — возможно, в настоящее время он не аварийно завершает работу с вашим текущим флагом компилятора и сборки, но он недействителен в соответствии со стандартом C++ и может перестать работать в любой момент. Такова природа неопределенного поведения; любое поведение приемлемо, в том числе поведение, ожидаемое вами, но нет никаких гарантий относительно поведения, которое вы наблюдаете в настоящее время или какое поведение вы можете наблюдать в будущем. Так что просто не пишите код с помощью UB. Простого тестирования кода и наблюдения за его поведением недостаточно, чтобы заявить, что программа является допустимой C++.

Jesper Juhl 07.06.2024 22:12
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
7
130
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Разыменование nullptr — это неопределенное поведение. Сбой не гарантирован, что-либо (буквально) может произойти, и компилятор не обязан предупреждать вас об этом - он может просто предположить, что этого никогда не произойдет (потому что, если бы это произошло, это означало бы, что вы нарушаете правила, и тогда это разрешено делать что угодно). А если функция проверяет наличие nullptr внутри, то это просто бессмысленно, поскольку this никогда не может находиться nullptr внутри функции-члена, поэтому любой разумный оптимизирующий компилятор просто оптимизирует любую такую ​​проверку.

Вероятно, в вашей нынешней ситуации вы хотите if (ActorPtr && ActorPtr->IsValidLowLevel()).

Этой логике я тоже следовал, но дело в том, что функция работает, если указатель действительно равен нулю. Я думаю, что один из комментаторов что-то заметил, говоря, что функция просматривает область памяти, которую занимает указатель, и проверяет, «кажется ли она uобъектом». Но ваша логика — это именно то, почему я был так озадачен тем, почему эта функция вообще работает, потому что она противоречит всему, чему меня учили в основах.

Wardy 07.06.2024 22:01

@wardy "но дело в том, что функция работает, если указатель действительно равен нулю" - UB позволяет происходить чему угодно, включая вызов функции и поведение так, как вы ожидаете - это включено в набор "чего угодно". Это не означает, что код не сломан и что поведение не может измениться в любой момент (смена компилятора, изменение уровня оптимизации компилятора, изменение версии компилятора и т. д. и т. п.). Код недействителен/сломан, и вы не можете полагаться на то поведение, которое наблюдаете в данный момент.

Jesper Juhl 07.06.2024 22:09

Еще раз спасибо за быстрый ответ! Если это так, то я не понимаю, почему в Unreal Engine должна быть встроенная функция, предназначенная для проверки нуля, если UB будет происходить всякий раз, когда указатель на самом деле равен нулю, а не является статической логической функцией, в которую можно поместить указатель. и проведите нулевую проверку таким образом

Wardy 07.06.2024 22:17

@wardy «Я не понимаю, почему в Unreal Engine есть встроенная функция, предназначенная для проверки нулевых значений» - я тоже не знаю, но могу придумать несколько потенциальных причин; Некомпетентность. Лень. Невежество. «Кажется, это работает для меня с моим подходом к цепочке инструментов (который во многом такой же, как и другие).

Jesper Juhl 07.06.2024 22:20

@Wardy Для получения более подробной информации я настоятельно советую вам прочитать страницу en.cppreference.com/w/cpp/language/ub, на которую я ссылаюсь (полностью), а также прочитать все очень хорошие статьи, которые связаны в конце. Это должно дать вам хорошее представление о UB в C++ (и C; но это все еще актуально) и об опасностях, связанных с этим. :-) Современные компиляторы активно эксплуатируют UB и довольно часто оптимизируют на его основе (не происходит) - ИМХО, знать об этом действительно важно.

Jesper Juhl 07.06.2024 22:29

Ваша помощь оценена по достоинству! Я дам им чек :)

Wardy 07.06.2024 22:42

@Wardy См. также blog.regehr.org/archives/213 (а также части 2 и 3).

Jesper Juhl 07.06.2024 23:12

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