Порядок вычисления
Порядок вычисления любой части любого выражения, включая порядок вычисления аргументов функции, является неопределённым (за некоторыми исключениями, перечисленными ниже). Компилятор может вычислять операнды и другие подвыражения в любом порядке и может выбрать другой порядок при повторном вычислении того же выражения.
В C++ нет понятия вычисления слева направо или справа налево. Не следует путать это с левоассоциативностью и правоассоциативностью операторов: выражение a() + b() + c() разбирается как (a() + b()) + c() из-за левоассоциативности operator+, но c() может вычисляться первым, последним или между a() и b() во время выполнения:
#include <cstdio>
int a() { return std::puts("a"); }
int b() { return std::puts("b"); }
int c() { return std::puts("c"); }
void z(int, int, int) {}
int main()
{
z(a(), b(), c()); // all 6 permutations of output are allowed
return a() + b() + c(); // all 6 permutations of output are allowed
}
Возможный вывод:
b c a c a b
Правила «упорядочено перед» (начиная с C++11)
Вычисление выражений
Вычисление каждого выражения включает:
- Вычисление значения: вычисление значения, возвращаемого выражением. Оно может включать определение идентичности объекта (вычисление glvalue, например, если выражение возвращает ссылку на объект) или чтение значения, ранее присвоенного объекту (вычисление prvalue, например, если выражение возвращает число или какое-либо другое значение).
- Инициирование побочных эффектов: обращение (чтение или запись) к объекту, обозначенному volatile glvalue, изменение (запись) объекта, вызов библиотечной функции ввода-вывода или вызов функции, выполняющей любую из этих операций.
Упорядочение
Упорядочено перед — это асимметричное транзитивное попарное отношение между вычислениями A и B в одном потоке.
- Если
Aупорядочено передB(или, что эквивалентно,Bупорядочено послеA), то вычислениеAзавершится до начала вычисленияB. - Если
Aне упорядочено передB, аBупорядочено передA, то вычислениеBзавершится до начала вычисленияA. - Если
Aне упорядочено передBиBне упорядочено передA, возможны два варианта:- Вычисления
AиBне упорядочены : они могут выполняться в любом порядке и могут перекрываться (в одном потоке выполнения компилятор может чередовать инструкции ЦП, составляющиеAиB). - Вычисления
AиBупорядочены неопределённо : они могут выполняться в любом порядке, но не могут перекрываться: либоAзавершится доB, либоBзавершится доA. При следующем вычислении того же выражения порядок может быть обратным.
- Вычисления
Если не указано иное, вычисления операндов отдельных операторов и подвыражений отдельных выражений не упорядочены.
| При вычислении выражения в качестве основного константного выражения подоперанды и подвыражения, которые в противном случае не упорядочены или упорядочены неопределённо, вычисляются в лексическом порядке. | (начиная с C++26) |
Правила
func (независимо от того, является ли функция встроенной и используется ли явный синтаксис вызова функции) каждый пункт следующего списка упорядочен перед следующим пунктом: - каждое выражение аргумента и постфиксное выражение, обозначающее
func
| (начиная с C++26) |
- каждое выражение или оператор в теле функции
func
| (начиная с C++26) |
&&, встроенного оператора логического ИЛИ || и встроенного оператора запятая , упорядочен перед вторым (правым) операндом.У правила 10 есть одно исключение: вызовы функций, выполняемые алгоритмом стандартной библиотеки с политикой выполнения std::execution::par_unseq, не упорядочены и могут произвольно чередоваться друг с другом. | (начиная с C++17) |
operator new) упорядочен неопределённым образом относительно(до C++17)упорядочен перед(начиная с C++17) вычислением аргументов конструктора в выражении new. 13) В выражении вызова функции выражение, задающее имя функции, упорядочено перед каждым выражением аргумента и каждым аргументом по умолчанию. 14) При вызове функции вычисления значений и побочные эффекты инициализации каждого параметра упорядочены неопределённым образом относительно вычислений значений и побочных эффектов инициализации любого другого параметра. 15) Каждый перегруженный оператор при вызове с использованием операторной нотации подчиняется правилам упорядочения встроенного оператора, который он перегружает. 16) В выражении индексирования E1[E2] E1 упорядочено перед E2. 17) В выражении указателя на член E1.*E2 или E1->*E2 E1 упорядочено перед E2. 18) В выражении оператора сдвига E1 << E2 и E1 >> E2 E1 упорядочено перед E2. 19) В каждом выражении простого присваивания E1 = E2 и каждом выражении составного присваивания E1 @= E2 E2 упорядочено перед E1. 20) При прямой инициализации неагрегатного класса каждое выражение в инициализаторе в круглых скобках вычисляется так, как если бы оно использовалось при вызове функции (упорядочено неопределённым образом). | (начиная с C++17) |
| 21) При прямой инициализации агрегата инициализация каждого элемента агрегата упорядочена перед инициализацией следующего элемента. | (начиная с C++20) |
Неопределённое поведение
Поведение является неопределённым в следующих случаях:
i = ++i + 2; // well-defined i = i++ + 2; // undefined behavior until C++17 f(i = -2, i = -2); // undefined behavior until C++17 f(++i, ++i); // undefined behavior until C++17, unspecified after C++17 i = ++i + i++; // undefined behavior
cout << i << i++; // undefined behavior until C++17 a[i] = i++; // undefined behavior until C++17 n = ++i + i; // undefined behavior
- побочный эффект над той же областью памяти
- вычисление значения, использующее значение любого объекта в той же области памяти
- начало или завершение времени жизни объекта, занимающего хранилище, которое пересекается с этой областью памяти
union U { int x, y; } u;
(u.x = 1, 0) + (u.y = 2, 0); // undefined behavior
Правила точек следования (до C++11)
Определения до C++11
Вычисление выражения может приводить к побочным эффектам: обращению к объекту, обозначенному volatile lvalue, изменению объекта, вызову библиотечной функции ввода-вывода или вызову функции, выполняющей любую из этих операций.
Точка следования — это точка в последовательности выполнения, в которой все побочные эффекты предыдущих вычислений в последовательности завершены, а побочные эффекты последующих вычислений ещё не начались.
Правила до C++11
a находится точка следования.a && b a || b a ? b : c a , b
Неопределённое поведение до C++11
Поведение является неопределённым в следующих случаях:
i = ++i + i++; // undefined behavior i = i++ + 1; // undefined behavior i = ++i + 1; // undefined behavior ++ ++i; // undefined behavior f(++i, ++i); // undefined behavior f(i = -1, i = -1); // undefined behavior
cout << i << i++; // undefined behavior a[i] = i++; // undefined behavior
Отчёты об ошибках стандарта
Следующие отчёты об ошибках, изменяющие поведение, были ретроактивно применены к ранее опубликованным стандартам C++.
| DR | Применён к | Поведение согласно опубликованному стандарту | Корректное поведение |
|---|---|---|---|
| CWG 1885 | C++11 | порядок уничтожения автоматических переменных при возврате из функции не был явно задан | добавлены правила упорядочения |
| CWG 1949 | C++11 | термин «упорядочено после» использовался, но не был определён в стандарте C++ | определён как обратное отношение к «упорядочено перед» |
| CWG 1953 | C++11 | побочные эффекты и вычисления значений, связанные с областью памяти, могли быть не упорядочены относительно начала или завершения времени жизни объекта в той же области памяти | в этом случае поведение является неопределённым |
| CWG 2146 | C++98 | в случаях неопределённого поведения не учитывались битовые поля | учтены |
Ссылки
- Стандарт C++23 (ISO/IEC 14882:2024):
- 6.9.1 Выполнение программы [intro.execution]
- 7.6.1.6 Инкремент и декремент [expr.post.incr]
- 7.6.2.8 New [expr.new]
- 7.6.14 Оператор логического И [expr.log.and]
- 7.6.15 Оператор логического ИЛИ [expr.log.or]
- 7.6.16 Условный оператор [expr.cond]
- 7.6.19 Операторы присваивания и составного присваивания [expr.ass]
- 7.6.20 Оператор запятая [expr.comma]
- 9.4.5 Инициализация списком [dcl.init.list]
- Стандарт C++20 (ISO/IEC 14882:2020):
- 6.9.1 Выполнение программы [intro.execution]
- 7.6.1.5 Инкремент и декремент [expr.post.incr]
- 7.6.2.7 New [expr.new]
- 7.6.14 Оператор логического И [expr.log.and]
- 7.6.15 Оператор логического ИЛИ [expr.log.or]
- 7.6.16 Условный оператор [expr.cond]
- 7.6.19 Операторы присваивания и составного присваивания [expr.ass]
- 7.6.20 Оператор запятая [expr.comma]
- 9.4.4 Инициализация списком [dcl.init.list]
- Стандарт C++17 (ISO/IEC 14882:2017):
- 4.6 Выполнение программы [intro.execution]
- 8.2.6 Инкремент и декремент [expr.post.incr]
- 8.3.4 New [expr.new]
- 8.14 Оператор логического И [expr.log.and]
- 8.15 Оператор логического ИЛИ [expr.log.or]
- 8.16 Условный оператор [expr.cond]
- 8.18 Операторы присваивания и составного присваивания [expr.ass]
- 8.19 Оператор запятая [expr.comma]
- 11.6.4 Инициализация списком [dcl.init.list]
- Стандарт C++14 (ISO/IEC 14882:2014):
- 1.9 Выполнение программы [intro.execution]
- 5.2.6 Инкремент и декремент [expr.post.incr]
- 5.3.4 New [expr.new]
- 5.14 Оператор логического И [expr.log.and]
- 5.15 Оператор логического ИЛИ [expr.log.or]
- 5.16 Условный оператор [expr.cond]
- 5.17 Операторы присваивания и составного присваивания [expr.ass]
- 5.18 Оператор запятая [expr.comma]
- 8.5.4 Инициализация списком [dcl.init.list]
- Стандарт C++11 (ISO/IEC 14882:2011):
- 1.9 Выполнение программы [intro.execution]
- 5.2.6 Инкремент и декремент [expr.post.incr]
- 5.3.4 New [expr.new]
- 5.14 Оператор логического И [expr.log.and]
- 5.15 Оператор логического ИЛИ [expr.log.or]
- 5.16 Условный оператор [expr.cond]
- 5.17 Операторы присваивания и составного присваивания [expr.ass]
- 5.18 Оператор запятая [expr.comma]
- 8.5.4 Инициализация списком [dcl.init.list]
См. также
- Приоритет операторов, определяющий, как выражения формируются из их исходного кода.
| Документация по C о порядке вычисления |
© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/cpp/language/eval_order