Что на самом деле означает «выполнение содержит неопределенную операцию»?

C++ (n4917.pdf), 4.1.2p5 (выделено нами):

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

Что на самом деле означает «выполнение содержит неопределенную операцию»?

Речь идет о наличии (т.е. без выполнения) неопределенной операции или о выполнении неопределенной операции?

Вы спрашиваете, является ли «казнь» про «казнь»?

Scott Hunter 22.08.2024 18:20

@ScottHunter Нет. Я спрашиваю, связано ли выражение «выполнение содержит неопределенную операцию» с наличием (т. е. без выполнения) неопределенной операции или с выполнением неопределенной операции.

pmor 22.08.2024 18:22

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

Mark Ransom 22.08.2024 18:25

@MarkRansom Спасибо за ссылку (я знаю, если она есть). Означает ли это, что «выполнение содержит неопределенную операцию» означает наличие (т.е. без выполнения) неопределенной операции?

pmor 22.08.2024 18:28

Я не понимаю, как «если какое-либо такое выполнение содержит неопределенную операцию» можно интерпретировать как «если отсутствие выполнения содержит неопределенную операцию». Существует много возможных исполнений абстрактной машины в ответ на вводимые данные, поскольку некоторые вещи оставлены на усмотрение реализации. Если какое-либо выполнение абстрактной машины содержит неопределенную операцию, то результаты программы не определены.

Raymond Chen 22.08.2024 18:29

Если ваш путь выполнения имеет UB, то и вся ваша программа имеет UB. Если UB никогда не выполняется, то и нет.

NathanOliver 22.08.2024 18:35

@RaymondChen Я перефазирую с «точки зрения компилятора»: если компилятор доказал, что программа содержит неопределенную операцию, которая будет выполнена, то может ли компилятор признать недействительной всю программу (например, пропустить/отменить/изменить операции, предшествующие первой неопределенная операция)? Другими словами: что такое «триггер аннулирования»: наличие (без выполнения) неопределенной операции ИЛИ фактическое выполнение неопределенной операции?

pmor 22.08.2024 18:39

Наличие без выполнения невалидной операции — это нормально. Ты делаешь это постоянно! if (p) return *p; Здесь присутствует недопустимая операция (разыменование нулевого указателя), но вы также убедились, что она никогда не выполняется (поместив ее внутри if). Однако если компилятор сможет доказать, что какой-либо конкретный *p происходит, когда p имеет значение null, то эта ошибка может повлиять на операции, которые предшествовали разыменованию. (Обратите внимание, что ваше «другими словами» отличается от части предложения «с точки зрения компилятора».)

Raymond Chen 22.08.2024 18:48

@NathanOliver Рассмотрим программу P, которая выполняет A, B, <неопределенную операцию>. Компилятор видит/доказывает, что P содержит неопределенную операцию, которая будет выполнена. Насколько я понимаю, в этом случае для C++ A и/или B могут быть признаны недействительными (т.е. пропущены/отменены/изменены). Мой вопрос: кому разрешено делать аннулирование: компилятору (т.е. во время компиляции; неопределенная операция не выполняется) ИЛИ самой программе (т.е. во время выполнения; неопределенная операция выполняется)? Или оба?

pmor 22.08.2024 18:56

Вы путаете выполнение на абстрактной машине с выполнением на физической машине. Если абстрактная машина выполняет неопределенную операцию, то физической машине разрешено делать все, что она хочет. «Все, что он хочет» включает в себя «сбой» или «не делать этого вообще». Это несоответствие между абстрактными и физическими машинами происходит постоянно благодаря правилу «как если бы». Абстрактная машина вычисляет 1+2, но физическая машина пропускает код операции сложения и использует только 3.

Raymond Chen 22.08.2024 19:01

@pmor Компилятор — это тот, кто выполняет «инвалидацию», поскольку C++ — это компилируемый язык.

NathanOliver 22.08.2024 19:02

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

NathanOliver 22.08.2024 19:03

@RaymondChen Спасибо. Итак, «выполнение содержит неопределенную операцию» означает, что такая «неопределенная операция» не обязательно (т.е. не требуется) фактически выполняться (на физической машине). Это правильно?

pmor 22.08.2024 19:07

Нет. Чтобы «существовать», UB должен быть выполнен.

NathanOliver 22.08.2024 19:09

@NathanOliver Вот что я пытаюсь выяснить: является ли «выполнение содержит неопределенную операцию» фактическим выполнением (скажем, на физической машине) неопределенной операции ИЛИ, например, доказательством / знанием того, что неопределенная операция будет быть выполнено (скажем, на физической машине).

pmor 22.08.2024 19:16

C++ не заботится о физической машине. Все, о чем мы говорим, — это абстрактная машина, которую использует стандарт C++ и которая определяет, что законно, а что нет. Если в соответствии с правилами абстрактной машины ваш путь выполнения проходит через выражение с неопределенным поведением, то все выполнение имеет неопределенное поведение. Если путь выполнения никогда не выполняет неопределенную операцию, то все в порядке, даже если в пути присутствует UB, который вы никогда не выполняете.

NathanOliver 22.08.2024 19:31

@NathanOliver Спасибо. Я перепутал абстрактную машину с конкретной (например, физической) машиной. Re: «все выполнение имеет неопределенное поведение»: напоминаем: это не верно для C: там должно появиться все наблюдаемое поведение, предшествующее неопределенной операции. Я не знал об этой разнице между C и C++ в отношении. УБ.

pmor 22.08.2024 20:09

Разница в том, что C++ делает всю программу UB, если какая-либо часть выполнения имеет UB. Это означает, что UB в C++ может «путешествовать во времени», и более ранний или поздний код не будет работать должным образом.

NathanOliver 22.08.2024 20:11

@NathanOliver Ради чего было сделано «создание всей программы UB»? Увеличение производительности? Меньше сложности компилятора?

pmor 22.08.2024 20:23

Я бы сказал, что скорее всего причина в сложности компилятора.

NathanOliver 22.08.2024 20:28
Стоит ли изучать 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
20
81
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

Абстрактная машина параметризуется аспектами, которые, как указано в стандарте, определены реализацией, см. [intro.abstract]/2.

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

Каждый такой экземпляр абстрактной машины может быть запущен для любой программы с любыми входными данными и не является детерминированным. Его недетерминированные аспекты — это поведение, которое в стандарте не указано. Затем каждая реализация всех неопределенных поведений определяет одно детерминированное выполнение на экземпляре абстрактной машины для данной программы и ввода, которые представляют собой операции, выполняемые абстрактной машиной в соответствии с правилами выполнения программы, указанными в остальной части стандарта с учетом реализаций. определяемого реализацией и неопределенного поведения для этой программы и ввода. См. [intro.abstract]/3.

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

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

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

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

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

Re: "нет требований к наблюдаемому поведению": в C есть (должен появиться OB, предшествующий неопределенной операции). Интересно, не предъявляет ли C++ (в данном случае) никаких требований к OB ради повышения производительности. Есть какие-нибудь знания/идеи/комментарии по этому поводу?

pmor 22.08.2024 20:18

Как прокомментировал пользователь Raymond Chen , я действительно путаю выполнение абстрактной машиной с выполнением конкретной (например, физической) машины.

Спрашивая (и комментируя), я думал о конкретной (например, физической) машине.

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

Это не имеет никакого отношения к тому, существует или нет «живой» код с неопределенным поведением. «Триггер» предназначен для каждого набора пользовательских входных данных. Вы не можете сказать, что вся программа признана недействительной, а только то, что она недействительна для данного набора входных данных. В частности, решается, содержит ли любое возможное выполнение абстрактной машины неопределенную операцию для данного реализованного пользовательского ввода. Просто наличие в программе некоторого кода с неопределенным поведением не имеет значения, если реализованный пользовательский ввод не приводит к его выполнению (на абстрактной машине).

user17732522 22.08.2024 20:06

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