Значение x*f(x) не определено, если f изменяет x?
Я посмотрел на кучу вопросов, касающихся точек последовательности, и не смог выяснить, если порядок оценки для x*f(x)
гарантируется, если f
модифицирует x
и отличается ли это для f(x)*x
,
Рассмотрим этот код:
#include <iostream>
int fx(int &x) {
x = x + 1;
return x;
}
int f1(int &x) {
return fx(x)*x; // Line A
}
int f2(int &x) {
return x*fx(x); // Line B
}
int main(void) {
int a = 6, b = 6;
std::cout << f1(a) << " " << f2(b) << std::endl;
}
Это печатает 49 42
на g++ 4.8.4 (Ubuntu 14.04).
Мне интересно, является ли это гарантированным поведением или неуказанным.
В частности, в этой программе fx
вызывается дважды, с x=6
оба раза и возвращает 7 оба раза. Разница в том, что строка A вычисляет 7*7 (принимая значение x
после fx
возвращает), в то время как строка B вычисляет 6*7 (принимая значение x
до fx
возвращается).
Это гарантированное поведение? Если да, то в какой части стандарта это указано?
Также: если я изменю все функции для использования int *x
вместо int &x
и внести соответствующие изменения в местах, из которых они вызваны, я получаю C
код, который имеет те же проблемы. Отличен ли ответ для C?
6 ответов
С точки зрения последовательности оценки легче думать о x*f(x)
как будто это было:
operator*(x, f(x));
так что нет математических предвзятых мнений о том, как умножение должно работать.
Как подсказывает @dan04, стандарт гласит:
Раздел 1.9.15: "За исключением отмеченных случаев, оценки операндов отдельных операторов и подвыражений отдельных выражений не являются последовательными".
Это означает, что компилятор может вычислять эти аргументы в любом порядке, причем точка последовательности operator*
вызов. Единственная гарантия заключается в том, что до operator*
называется, оба аргумента должны быть оценены.
В вашем примере, концептуально, вы можете быть уверены, что хотя бы один из аргументов будет равен 7, но вы не можете быть уверены, что оба они будут. Для меня этого было бы достаточно, чтобы обозначить это поведение как неопределенное; однако ответ @user2079303 хорошо объясняет, почему это технически не так.
Независимо от того, является ли поведение неопределенным или неопределенным, вы не можете использовать такое выражение в хорошо управляемой программе.
Порядок оценки аргументов не указан стандартом, поэтому поведение, которое вы видите, не гарантируется.
Поскольку вы упоминаете точки последовательности, я рассмотрю стандарт C++03, который использует этот термин, в то время как более поздние стандарты изменили формулировку и отказались от этого термина.
ISO / IEC 14882: 2003 (E) §5 / 4:
Если не указано иное, порядок вычисления операндов отдельных операторов и подвыражений отдельных выражений и порядок возникновения побочных эффектов не определен...
Существует также дискуссия о том, является ли это неопределенным поведением или это просто неопределенный порядок. Остальная часть этого параграфа проливает некоторый свет (или сомнение) на это.
ISO / IEC 14882: 2003 (E) §5 / 4:
... Между предыдущей и следующей точкой последовательности скалярному объекту должно быть изменено его сохраненное значение не более одного раза путем вычисления выражения. Кроме того, предварительное значение должно быть доступно только для определения значения, которое будет сохранено. Требования этого параграфа должны выполняться для каждого допустимого упорядочения подвыражений полного выражения; в противном случае поведение не определено.
x
действительно изменен в f
и это значение читается как операнд в том же выражении, где f
называется. И не указано, x
читает измененное или неизмененное значение. Это может кричать Неопределенное поведение! вам, но держите лошадей, потому что стандарт также гласит:
ISO / IEC 14882: 2003 (E) §1.9 / 17:
... При вызове функции (независимо от того, является ли функция встроенной), после вычисления всех аргументов функции (если таковые имеются) существует точка последовательности, которая имеет место до выполнения каких-либо выражений или операторов в теле функции. Существует также точка последовательности после копирования возвращенного значения и перед выполнением любых выражений вне функции 11)...
Так что если f(x)
сначала оценивается, затем после копирования возвращаемого значения появляется точка последовательности. Таким образом, вышеприведенное правило о UB не применяется, потому что чтение x
не находится между следующей и предыдущей точкой последовательности. x
операнд будет иметь измененное значение.
Если x
сначала оценивается, затем после оценки аргументов f(x)
Опять же, правило о UB не применяется. В этом случае x
операнд будет иметь неизмененное значение.
Таким образом, порядок не указан, но нет неопределенного поведения. Это ошибка, но результат в некоторой степени предсказуем. Поведение является таким же в более поздних стандартах, хотя формулировка изменилась. Я не буду вдаваться в подробности, поскольку это уже хорошо освещено в других хороших ответах.
Так как вы спрашиваете о похожей ситуации в C
С89 (проект) 3.3/3:
За исключением случаев, обозначенных синтаксисом 27 или иным образом заданным позже (для операторов вызова функции (), &&, ||,?: И операторов запятой), порядок вычисления подвыражений и порядок возникновения побочных эффектов составляют оба не указаны.
Исключение вызова функции уже упоминалось здесь. Ниже приведен абзац, который подразумевает неопределенное поведение, если не было точек последовательности:
С89 (проект) 3.3/2:
Между предыдущей и следующей точкой последовательности объект должен иметь свое сохраненное значение, измененное не более одного раза путем оценки выражения. Кроме того, предварительное значение должно быть доступно только для определения значения, которое будет сохранено.26
И вот точки последовательности определены:
С89 (черновик) А.2
Ниже приведены точки последовательности, описанные в 2.1.2.3.
Вызов функции после оценки аргументов (3.3.2.2).
...
... выражение в операторе возврата (3.6.6.4).
Выводы такие же, как в C++.
Небольшая заметка о том, что я не вижу, явно покрыто другими ответами:
если порядок оценки для
x*f(x)
гарантируется, еслиf
модифицируетx
и отличается ли это дляf(x)*x
,
Посмотрим, как в ответе Максима
operator*(x, f(x));
теперь есть только два способа оценки обоих аргументов перед вызовом, как требуется:
auto lhs = x; // or auto rhs = f(x);
auto rhs = f(x); // or auto lhs = x;
return lhs * rhs
Итак, когда вы спрашиваете
Мне интересно, является ли это гарантированным поведением или неуказанным.
стандарт не определяет, какое из этих двух поведений должен выбрать компилятор, но он указывает, что это единственное допустимое поведение.
Таким образом, это ни гарантировано, ни совершенно не определено.
О, и:
Я посмотрел на кучу вопросов, касающихся точек последовательности, и не смог выяснить, если порядок оценки...
точки последовательности используются в трактовке этого стандарта языка C, но не в стандарте C++.
В выражении x * y
условия x
а также y
не секвенированы. Это одно из трех возможных отношений секвенирования:
A
секвенировали-перед темB
:A
должны быть оценены, все побочные эффекты завершены, доB
начинается оценкаA
а такжеB
неопределенно упорядоченный: верно одно из двух следующих случаев:A
последовательность передB
, или жеB
последовательность передA
, Не определено, какой из этих двух случаев имеет место.A
а такжеB
unsequenced: нет последовательности, определенной междуA
а такжеB
,
Важно отметить, что это парные отношения. Мы не можем сказать " x
является неупорядоченным ". Мы можем только сказать, что две операции не упорядочены по отношению друг к другу.
Также важно, что эти отношения являются переходными; и последние два отношения симметричны.
неопределенный - это технический термин, означающий, что в стандарте указано определенное количество возможных результатов. Это отличается от неопределенного поведения, что означает, что Стандарт не охватывает поведение вообще. Смотрите здесь для дальнейшего чтения.
Переходя на код x * f(x)
, Это идентично f(x) * x
потому что, как обсуждалось выше, x
а также f(x)
не последовательны по отношению друг к другу в обоих случаях.
Теперь мы подошли к моменту, когда несколько человек, кажется, отклеились. Оценивая выражение f(x)
не упорядочен по отношению к x
, Однако из этого не следует, что какие-либо утверждения внутри тела функции f
также не последовательны в отношении x
, Фактически, существуют последовательности отношений, окружающие любой вызов функции, и эти отношения нельзя игнорировать.
Вот текст из C++14:
При вызове функции (независимо от того, является ли функция встроенной), каждое вычисление значения и побочный эффект, связанный с любым выражением аргумента или с выражением постфикса, обозначающим вызываемую функцию, упорядочивается перед выполнением каждого выражения или оператора в теле вызываемая функция. [Примечание: вычисления значений и побочные эффекты, связанные с различными выражениями аргументов, не являются последовательными. - примечание к концу] Каждая оценка в вызывающей функции (включая другие вызовы функций), которая иначе специально не упорядочена до или после выполнения тела вызываемой функции, определяется неопределенным образом по отношению к выполнению вызываемой функции.
со сноской:
Другими словами, выполнение функций не чередуется друг с другом.
Текст, выделенный жирным шрифтом, четко указывает, что для двух выражений:
- A:
x = x + 1;
внутриf(x)
- B: оценка первого
x
в выраженииx * f(x)
их отношения: неопределенно упорядочены.
Текст относительно неопределенного поведения и последовательности:
Если побочный эффект на скалярный объект не секвенирован относительно другого побочного эффекта на тот же скалярный объект или вычисления значения с использованием значения того же скалярного объекта, и они не являются потенциально параллельными (1.10), поведение не определено.
В этом случае отношение является неопределенным, а не последовательным. Так что нет неопределенного поведения.
Результат вместо этого уточняется в зависимости от того, x
последовательность перед x = x + 1
или наоборот. Таким образом, есть только два возможных результата, 42
а также 49
,
В случае, если у кого-то были сомнения по поводу x
в f(x)
, применяется следующий текст:
При вызове функции (независимо от того, является ли функция встроенной), каждое вычисление значения и побочный эффект, связанный с любым выражением аргумента или с выражением постфикса, обозначающим вызываемую функцию, упорядочивается перед выполнением каждого выражения или оператора в теле вызываемая функция.
Таким образом, оценка этого x
последовательность перед x = x + 1
, Это пример восклицания, которое подпадает под случай "специально упорядоченного ранее " в выделенной жирным шрифтом цитате выше.
Сноска: поведение было точно таким же в C++03, но терминология была другой. В C++ 03 мы говорим, что существует точка последовательности при входе и выходе каждого вызова функции, поэтому запись в x
внутри функция отделена от чтения x
вне функции хотя бы на одну точку последовательности.
Вы должны различать:
а) Приоритет операторов и ассоциативность, которые управляют порядком, в котором значения подвыражений объединяются их операторами.
б) Последовательность оценки подвыражения. Например, в выражении f(x)/g(x)
Компилятор может оценить g(x)
первый и f(x)
после этого. Тем не менее, результирующее значение должно быть вычислено путем деления соответствующих вложенных значений в правильном порядке, конечно.
в) Последовательность побочных эффектов подвыражений. Грубо говоря, например, компилятор может ради оптимизации решить записать значения в затронутые переменные только в конце выражения или в любом другом подходящем месте.
В качестве очень приблизительного приближения можно сказать, что в пределах одного выражения порядок оценки (не ассоциативность и т. Д.) Более или менее не определен. Если вам нужен конкретный порядок оценки, разбейте выражение на ряд утверждений, например:
int a = f(x);
int b = g(x);
return a/b;
вместо
return f(x)/g(x);
Точные правила см. На http://en.cppreference.com/w/cpp/language/eval_order
Порядок вычисления операндов почти всех операторов C++ не определен. Компилятор может оценивать операнды в любом порядке и может выбирать другой порядок, когда то же выражение вычисляется снова
Поскольку порядок оценки не всегда одинаков, следовательно, вы можете получить неожиданные результаты.