В Порядке оценки не нашла соответствующих терминов.
Итак, является ли поведение функции g
неопределенным в приведенном ниже коде?
int x;
int f() { return x++; }
void g() { x = f(); }
Я скомпилировал код на другой платформе и с разными аргументами, и оказалось, что x
всегда оставался неизменным.
В выражении x = f()
оценка f()
располагается перед оценкой =
. Побочные эффекты внутри f()
не являются побочными эффектами этого выражения. См. раздел [intro.execution]
стандарта, соответствующее правило, по-видимому, находится в параграфе 18 окончательного варианта C++17 («При вызове функции...»).
@user207421 user207421 какой конкретно термин может объяснить порядок между (c) и (d)?
обратите внимание, что unspecified и undefine имеют тонкую, но важную разницу в значении. stackoverflow.com/questions/2397984/…
@463035818_is_not_an_ai Если побочный эффект f
и присваивание в g
неупорядочены, то это наверняка уб по стандарту.
return x++;
это выражение должно закончиться, прежде чем запустится что-либо еще.
У вас есть серия выражений, значение которых равно x
, за которым следует присвоение x
. Пост-инкремент x
не может иметь значения, хотя он имеет место.
Могут ли близкие избиратели объяснить, почему это не воспроизводится, является опечаткой или «решено таким образом, что вряд ли поможет будущим читателям»?
@nalemy — Обратите внимание, что x++
возвращает старое значение x
, поэтому технически оно увеличивается, но затем снова присваивается старое значение в g
. Таким образом, без изменений. (Конечно, компилятор может поступить умно и оптимизировать код, зная, что приращение потеряно, и пропустить его).
Этот код определил поведение.
Начиная с C++11, правилом является то, что побочный эффект присваивания располагается после вычисления значения, а не побочные эффекты обоих операндов. Вызов функции, который не упорядочен до или после другого выражения, является неопределенно упорядоченным относительно этого вычисления, а не неупорядоченным. Оно не определено только в том случае, если два неупорядоченных выражения изменяют одну и ту же ячейку памяти.
Правило, существовавшее до C++11, гласит, что после вызова функции существует точка последовательности.
Вычисление значения и побочные эффекты выражения f()
в g
возникают в результате выполнения тела f
, т. е. x++
.
Учитывая, что опубликованный код четко определен на C и всегда был таковым (из-за точки последовательности после оценки аргумента и еще одной перед возвратом функции, и, вероятно, еще кучи таких в теле функции), что именно в C++ сделано это неопределенно до C++17? Некоторые источники были бы хороши.
@Лундин, я не был уверен. Оказывается, это было определено с самого начала, но по разным причинам.
На самом деле cppreference это ясно отмечает. В 19-м термине говорилось, что: В каждом простом выражении присваивания E1 = E2
и каждом составном выражении присваивания E1 @= E2
каждое вычисление значения и побочный эффект E2
располагаются перед каждым вычислением значения и побочным эффектом E1
.
@nalemy нет, это операнды присваивания, а не побочный эффект самого присваивания. В пункте 8 действительно должна быть рамка «до C++17» вокруг «(но не побочных эффектов)».
Более того, указано или запрещено стандартом C++ «побочные эффекты упорядочиваются перед вычислением функции»?
@nalemy да, это в [expr.ass]
Если оставить в стороне оператор присваивания, точки последовательности сделали все совершенно ясным, но в C++11 вы не можете легко понять, как все упорядочено по отношению друг к другу: оценка аргументов, тело функции, включая побочные эффекты, оператор возврата функции, оценка результатов. Это разбросано по всему ISO 14882 в беспорядке «последовательно до/после». Типичный метод работы C++ WG: найти что-то, что ранее было четко определено, и сделать его неопределенным. Желательно спрятав, разбросав или просто забыв включить соответствующую информацию.
Точки последовательности @Lundin не позволят вам указать какой-либо порядок памяти, кроме seq_cst
является ли поведение функции 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
является глобальным, он никогда не инициализируется. Глобальные переменные инициализируются нулями.
Я никогда не обращал на это внимания (потому что никогда не использовал глобальные переменные в C++). Ты прав! Спасибо, что указали на это. Переменные со статической или потоковой длительностью хранения инициализируются нулем (в случае скаляра). Все переменные, объявленные в области пространства имен, имеют статический срок хранения. (См.: 6.7.5.2 [basic.stc.static] ), а глобальная область видимости — это область имен глобального пространства имен (см.: 6.4.6 [basic.scope.namespace]).
Нет, это не так. Это было (а) оценено в
f()
; (б) возвращеноf()
; (c) добавлен пост-инкремент вf()
; и (d) сохраняется с исходным значением вg()
.