Указано ли в стандарте C++ «Побочные эффекты функции упорядочиваются перед ее вычислением»?

В Порядке оценки не нашла соответствующих терминов.

Итак, является ли поведение функции g неопределенным в приведенном ниже коде?

int x;
int f() { return x++; }
void g() { x = f(); }

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

Нет, это не так. Это было (а) оценено в f(); (б) возвращено f(); (c) добавлен пост-инкремент в f(); и (d) сохраняется с исходным значением в g().

user207421 17.04.2024 12:25

В выражении x = f() оценка f() располагается перед оценкой =. Побочные эффекты внутри f() не являются побочными эффектами этого выражения. См. раздел [intro.execution] стандарта, соответствующее правило, по-видимому, находится в параграфе 18 окончательного варианта C++17 («При вызове функции...»).

Abstraction 17.04.2024 12:27

@user207421 user207421 какой конкретно термин может объяснить порядок между (c) и (d)?

nalemy 17.04.2024 12:31

обратите внимание, что unspecified и undefine имеют тонкую, но важную разницу в значении. stackoverflow.com/questions/2397984/…

463035818_is_not_an_ai 17.04.2024 12:34

@463035818_is_not_an_ai Если побочный эффект f и присваивание в g неупорядочены, то это наверняка уб по стандарту.

nalemy 17.04.2024 12:43
return x++; это выражение должно закончиться, прежде чем запустится что-либо еще.
KamilCuk 17.04.2024 12:51

У вас есть серия выражений, значение которых равно x, за которым следует присвоение x. Пост-инкремент x не может иметь значения, хотя он имеет место.

user207421 17.04.2024 13:30

Могут ли близкие избиратели объяснить, почему это не воспроизводится, является опечаткой или «решено таким образом, что вряд ли поможет будущим читателям»?

Passer By 17.04.2024 14:31

@nalemy — Обратите внимание, что x++ возвращает старое значение x, поэтому технически оно увеличивается, но затем снова присваивается старое значение в g. Таким образом, без изменений. (Конечно, компилятор может поступить умно и оптимизировать код, зная, что приращение потеряно, и пропустить его).

BoP 17.04.2024 14:40
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
9
145
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Этот код определил поведение.

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

Правило, существовавшее до C++11, гласит, что после вызова функции существует точка последовательности.

Вычисление значения и побочные эффекты выражения f() в g возникают в результате выполнения тела f, т. е. x++.

Учитывая, что опубликованный код четко определен на C и всегда был таковым (из-за точки последовательности после оценки аргумента и еще одной перед возвратом функции, и, вероятно, еще кучи таких в теле функции), что именно в C++ сделано это неопределенно до C++17? Некоторые источники были бы хороши.

Lundin 17.04.2024 12:53

@Лундин, я не был уверен. Оказывается, это было определено с самого начала, но по разным причинам.

Caleth 17.04.2024 13:01

На самом деле cppreference это ясно отмечает. В 19-м термине говорилось, что: В каждом простом выражении присваивания E1 = E2 и каждом составном выражении присваивания E1 @= E2 каждое вычисление значения и побочный эффект E2 располагаются перед каждым вычислением значения и побочным эффектом E1.

nalemy 17.04.2024 13:06

@nalemy нет, это операнды присваивания, а не побочный эффект самого присваивания. В пункте 8 действительно должна быть рамка «до C++17» вокруг «(но не побочных эффектов)».

Caleth 17.04.2024 13:07

Более того, указано или запрещено стандартом C++ «побочные эффекты упорядочиваются перед вычислением функции»?

nalemy 17.04.2024 13:14

@nalemy да, это в [expr.ass]

Caleth 17.04.2024 13:24

Если оставить в стороне оператор присваивания, точки последовательности сделали все совершенно ясным, но в C++11 вы не можете легко понять, как все упорядочено по отношению друг к другу: оценка аргументов, тело функции, включая побочные эффекты, оператор возврата функции, оценка результатов. Это разбросано по всему ISO 14882 в беспорядке «последовательно до/после». Типичный метод работы C++ WG: найти что-то, что ранее было четко определено, и сделать его неопределенным. Желательно спрятав, разбросав или просто забыв включить соответствующую информацию.

Lundin 17.04.2024 13:42

Точки последовательности @Lundin не позволят вам указать какой-либо порядок памяти, кроме seq_cst

Caleth 17.04.2024 14:27
Ответ принят как подходящий

является ли поведение функции g неопределенным в приведенном ниже коде?

Нет.

указано ли в стандарте C++ «Побочные эффекты функции упорядочиваются перед ее вычислением»?

Да.

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

До C++11 и C у нас были «точки последовательности». Каждое полное выражение заканчивается точкой последовательности. В конце полного выражения return x++ в ; необходимо оценить все побочные эффекты.

После C++17 у нас есть это из https://timsong-cpp.github.io/cppwp/n4659/intro.execution#18:

Для каждого вызова функции F, для каждой оценки A, которая происходит внутри F, и для каждой оценки B, которая не происходит внутри F, но оценивается в том же потоке и как часть одного и того же обработчика сигнала (если таковой имеется), либо A упорядочивается перед B. или B располагается перед A

Значение f() должно быть оценено перед присвоением x в x = f(). Таким образом, все оценки внутри f() должны выполняться до назначения x. Включая любые побочные эффекты от return x++;.

int x;
int f() { return x++; }
void g() { x = f(); }

Поведение четко определено.
Вызов g() оставит x без изменений.
В void g() вы присваиваете значение, возвращаемое int f(), x.
Это эквивалентно написанию void g() { int y = f(); x = y; }

Вычисление значения выражения x++ выполняется до побочного эффекта (т. е. приращение [++] выражения x происходит после вычисления значения выражения x).
Это означает, что выполнение f() вернет значение x до того, как оно будет увеличено на 1.

Вы можете написать функцию следующим образом:

int f() { const int y = x; x = x + 1; return y; }

Наблюдаемое поведение гарантированно будет идентичным.

x является глобальным, он никогда не инициализируется. Глобальные переменные инициализируются нулями.
Sebastian Redl 17.04.2024 22:43

Я никогда не обращал на это внимания (потому что никогда не использовал глобальные переменные в C++). Ты прав! Спасибо, что указали на это. Переменные со статической или потоковой длительностью хранения инициализируются нулем (в случае скаляра). Все переменные, объявленные в области пространства имен, имеют статический срок хранения. (См.: 6.7.5.2 [basic.stc.static] ), а глобальная область видимости — это область имен глобального пространства имен (см.: 6.4.6 [basic.scope.namespace]).

viraltaco_ 18.04.2024 13:23

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

Clang не удается создать экземпляр `operator!=()` из `operator==()` с типом возвращаемого значения `auto` для объекта класса, переданного через параметр шаблона, не относящийся к типу
Почему Rust не меняет порядок полей в перечислении для размещения в памяти?
Почему C++23 if consteval не допускает разные типы возврата, как если бы constexpr подходил?
В C++ все подвыражения аргументов вызова функции последовательно упорядочены?
Каковы точные условия, при которых оценивается type_name в sizeof(type_name)? GCC оценивает f() в sizeof(int [(f(), 100)])
Можете ли вы объявить указатель C с собственным адресом?
Почему выражение с запятой, используемое в качестве размера массива, должно быть заключено в круглые скобки, если оно является частью декларатора массива?
Наследование конструкторов и пересылка ссылок
Почему приведение указателя члена объединения к указателю объединения не является UB, поскольку они могут иметь разный размер?
Указатель на отвратительный тип функции? Ошибка компилятора?