Подтвердить что ты не робот

Является ли значение 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 обоих раз. Разница в том, что Line A вычисляет 7 * 7 (принимая значение x после возврата fx), в то время как Line B вычисляет 6 * 7 (принимая значение x до возврата fx).

Это гарантированное поведение? Если да, то какая часть стандарта указывает это?

Также: если я изменю все функции, чтобы использовать int *x вместо int &x и внести соответствующие изменения в те места, из которых они вызваны, я получаю код C, который имеет те же проблемы. Является ли ответ другим для C?

4b9b3361

Ответ 1

В терминах последовательности оценки легче думать о x*f(x), как если бы это было:

operator*(x, f(x));

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

Как пояснил @dan04, в стандарте говорится:

Раздел 1.9.15: "За исключением тех случаев, когда отмечено, оценки операндов отдельных операторов и подвыражений отдельных выражений не имеют последствий".

Это означает, что компилятор может свободно оценивать эти аргументы в любом порядке, а точка последовательности - operator*. Единственная гарантия заключается в том, что до вызова operator* оба аргумента должны быть оценены.

В вашем примере, концептуально, вы можете быть уверены, что хотя бы один из аргументов будет равен 7, но вы не можете быть уверены, что оба они будут. Для меня этого было бы достаточно, чтобы обозначить это поведение как undefined; однако, @user2079303 ответ объясняет, почему это технически не так.

Независимо от того, является ли поведение undefined или неопределенным, вы не можете использовать такое выражение в хорошо управляемой программе.

Ответ 2

Порядок оценки аргументов не, указанный стандартом, поэтому поведение, которое вы видите, не гарантируется.

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

ISO/IEC 14882: 2003 (E) §5/4:

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


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

ISO/IEC 14882: 2003 (E) §5/4:

... Между предыдущей и следующей точками последовательности скалярный объект должен иметь значение, которое его хранимое значение изменялось не более одного раза путем оценки выражения. Кроме того, к предыдущему значению следует обращаться только для определения значения, которое необходимо сохранить. Требования настоящего параграфа удовлетворяются для каждого допустимого упорядочения подвыражений полного выражения; в противном случае поведение undefined.

x действительно изменен в f, и его значение считывается как операнд в том же выражении, где вызывается f. И не указано, читает ли x измененное или немодифицированное значение. Это может кричать undefined Поведение! вам, но держите лошадей, потому что в стандарте также говорится:

ISO/IEC 14882: 2003 (E) §1.9/17:

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

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

Если сначала оценивается x, то после оценки аргументов f(x) есть точка последовательности. Опять же, правило о UB не применяется. В этом случае операнд x будет иметь немодифицированное значение.

Таким образом, порядок не указан, но существует no undefined поведение. Это ошибка, но результат в некоторой степени предсказуем. Поведение такое же в более поздних стандартах, хотя формулировка изменилась. Я не буду вникать в них, поскольку он уже хорошо освещен в других хороших ответах.


Так как вы спрашиваете о подобной ситуации в C

C89 (черновик) 3.3/3:

За исключением случаев, указанных в синтаксисе 27 или иным образом указанном ниже (для операторов функции-вызова(), &, ||,? и запятых), порядок оценка подвыражений и порядок, в которых происходят побочные эффекты, не определены.

Исключение вызова функции уже упоминалось здесь. Ниже приведен абзац, который подразумевает поведение undefined, если не было точек последовательности:

C89 (черновик) 3.3/2:

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

И здесь определены следующие точки последовательности:

C89 (черновик) A.2

Ниже приведены точки последовательности, описанные в 2.1.2.3

  • Вызов функции после оценки аргументов (3.3.2.2).

  • ...

  • ... выражение в операторе return (3.6.6.4).

Выводы те же, что и в С++.

Ответ 3

Быстрая заметка о том, что я не вижу, явно рассматривается другими ответами:

если порядок оценки для 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

Итак, когда вы спрашиваете

Мне интересно, гарантировано ли это поведение или неуказано.

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

Таким образом, он не гарантирован и полностью не определен.


Oh и:

Я посмотрел на кучу вопросов о точках последовательности и не смог выяснить, есть ли порядок оценки...

точки последовательности используются в стандартном разрешении языка C, но не в стандарте С++.

Ответ 4

В выражении x * y члены x и y не подвержены влиянию. Это одно из трех возможных отношений секвенирования:

  • A sequenced-before B: A должен быть оценен, со всеми побочными эффектами завершен, прежде чем B начнет оценку g
  • A и B неопределенно-секвенированы: один из двух следующих случаев: true: A секвенирован до B или B секвенирован до A. Не определено, какой из этих двух случаев имеет место.
  • A и B unsequenced: отношения A и B не определены.

Важно отметить, что это паретические отношения. Мы не можем сказать, что "x не имеет последствий". Мы можем только сказать, что две операции не зависят друг от друга.

Также важно, чтобы эти отношения были transitive; и последние два соотношения симметричны.


unspecified - технический термин, который означает, что стандарт задает определенное количество возможных результатов. Это отличается от поведения undefined, что означает, что стандарт вообще не покрывает поведение. Смотрите здесь для дальнейшего чтения.


Переход на код x * f(x). Это идентично f(x) * x, потому что, как обсуждалось выше, x и f(x) в обоих случаях не зависят друг от друга.

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

Вот текст из С++ 14:

При вызове функции (независимо от того, является ли функция встроенной) каждое вычисление значения и побочный эффект, связанный с любым выражением аргумента, или с выражением postfix, обозначающим вызываемую функцию, секвенируются перед выполнением каждого выражения или оператора в тело вызываемой функции. [Примечание. Вычисления значений и побочные эффекты, связанные с разными выражениями аргументов, не имеют никакого значения. -end note] Каждая оценка в вызывающей функции (включая другие вызовы функций), которая иначе не секретируется отдельно до или после выполнения тела вызываемой функции, неопределенно упорядочена с помощью в отношении выполнения вызываемой функции.

со сноской:

Другими словами, выполнение функций не чередуется друг с другом.

Жирный текст ясно указывает, что для двух выражений:

  • A: x = x + 1; внутри f(x)
  • B: оценка первого x в выражении x * f(x)

их взаимосвязь: неопределенно упорядочена.

Текст, касающийся поведения и последовательности undefined:

Если побочный эффект скалярного объекта не зависит от другого побочного эффекта для одного и того же скалярного объекта или вычисления значения с использованием значения одного и того же скалярного объекта, и они не являются потенциально параллельными (1.10), поведение undefined.

В этом случае отношение неопределенно секвенировано, а не является нелогичным. Таким образом, поведение undefined отсутствует.

Результат вместо этого не задан в соответствии с тем, что x секвенировано до x = x + 1 или наоборот. Таким образом, возможны только два возможных результата: 42 и 49.


В случае, если кто-то сомневается в x в f(x), применяется следующий текст:

При вызове функции (независимо от того, является ли функция встроенной) каждое вычисление значения и побочный эффект, связанный с любым выражением аргумента, или с выражением postfix, обозначающим вызываемую функцию, секвенируются перед выполнением каждого выражения или оператора в тело вызываемой функции.

Итак, оценка этого x секвенирована до x = x + 1. Это пример evlauation, который подпадает под случай "специально упорядоченного до" в выделенной полужирной цитате.


Сноска: поведение было точно таким же в С++ 03, но терминология была иной. В С++ 03 мы говорим, что есть точка последовательности при входе и выходе каждого вызова функции, поэтому запись в x внутри функции отделена от чтения x вне функции по меньшей мере одной точкой последовательности.

Ответ 5

Вам нужно различать:

a) Приоритет и ассоциативность операторов, который управляет порядком, в котором значения подвыражений объединяются их операторами.

b) Последовательность оценки подвыражения. Например. в выражении f(x)/g(x) компилятор может сначала оценить g(x) и f(x). Тем не менее, результирующее значение должно быть рассчитано путем деления соответствующих подначислений в правильном порядке, конечно.

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

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

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

Ответ 6

Порядок оценки операндов почти всех операторов C++ неопределенные. Компилятор может оценивать операнды в любом порядке и может выберите другой порядок, когда снова будет оценено одно и то же выражение

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

Порядок оценки