Namespaces
Variants

Order of evaluation

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Value categories
Order of evaluation
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

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

В 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());       // допустимы все 6 перестановок вывода
    return a() + b() + c(); // допустимы все 6 перестановок вывода
}

Возможный вывод:

b
c
a
c
a 
b

Содержание

Правила "sequenced before" (начиная с C++11)

Вычисление выражений

Оценка каждого выражения включает:

  • Вычисления значений : вычисление значения, возвращаемого выражением. Это может включать определение идентификатора объекта (glvalue evaluation, например, если выражение возвращает ссылку на некоторый объект) или чтение значения, ранее присвоенного объекту (prvalue evaluation, например, если выражение возвращает число или другое значение).
  • Инициация побочных эффектов : доступ (чтение или запись) к объекту, обозначенному volatile glvalue, модификация (запись) объекта, вызов библиотечной функции ввода-вывода или вызов функции, выполняющей любую из этих операций.

Упорядочивание

Sequenced before — это асимметричное, транзитивное, парное отношение между вычислениями 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 . Порядок может быть противоположным при следующем вычислении того же выражения.

Выражение X называется упорядоченным перед выражением Y , если каждое вычисление значения и каждый побочный эффект, связанный с X , упорядочены перед каждым вычислением значения и каждым побочным эффектом, связанным с выражением Y .

Правила

1) Каждое полное выражение упорядочено перед следующим полным выражением.
2) Вычисление значений (но не побочные эффекты) операндов любого оператора упорядочены до вычисления значения результата оператора (но не его побочных эффектов).
3) При вызове функции func (независимо от того, является ли функция встроенной, и используется ли явный синтаксис вызова функции), каждый элемент в следующем списке выполняется перед следующим элементом:
  • каждое выражение аргумента и постфиксное выражение, обозначающее func
(начиная с C++26)
  • каждое выражение или оператор в теле функции func
(начиная с C++26)
4) Вычисление значения встроенных постфиксных операторов инкремента и декремента упорядочено до их побочного эффекта.
5) Побочный эффект встроенных пре-инкремента и пре-декремента операторов упорядочен перед вычислением их значения (неявное правило в силу определения как составного присваивания).
6) Первый (левый) операнд встроенного логического оператора И && , встроенного логического оператора ИЛИ || и встроенного оператора запятой , вычисляется до второго (правого) операнда.
7) Первый операнд в условном операторе ?: упорядочивается перед вторым или третьим операндом.
8) Побочный эффект (модификация левого аргумента) встроенного оператора присваивания и всех встроенных составных операторов присваивания упорядочен после вычисления значения (но не побочных эффектов) как левого, так и правого аргументов, и упорядочен до вычисления значения выражения присваивания (то есть до возвращения ссылки на модифицированный объект).
9) При list-initialization каждое вычисление значения и побочный эффект заданного инициализирующего выражения упорядочены перед любым вычислением значения и побочным эффектом, связанным с любым последующим инициализирующим выражением в заключённом в фигурные скобки списке инициализаторов, разделённых запятыми.
10) Вызов функции, который не является упорядоченным до или упорядоченным после другого вычисления выражения вне функции (возможно, другого вызова функции), является неопределённо упорядоченным относительно этого вычисления (программа должна вести себя как если бы машинные инструкции, составляющие вызов функции, не перемежались с инструкциями, составляющими вычисления других выражений, включая другие вызовы функций, даже если функция была встроенной).
Правило 10 имеет одно исключение: вызовы функций, выполняемые алгоритмом стандартной библиотеки с политикой выполнения std::execution::par_unseq , являются неупорядоченными и могут произвольно перемежаться друг с другом. (начиная с C++17)
11) Вызов функции выделения памяти ( operator new ) недетерминированно упорядочен относительно (до C++17) упорядочен перед (начиная с C++17) вычислением аргументов конструктора в new выражении .
12) При возврате из функции копирующая инициализация временного объекта, который является результатом вычисления вызова функции, упорядочена перед уничтожением всех временных объектов в конце операнда return statement , что, в свою очередь, упорядочено перед уничтожением локальных переменных блока, содержащего return statement.
13) В выражении вызова функции выражение, которое именует функцию, упорядочено перед каждым выражением аргумента и каждым аргументом по умолчанию.
14) При вызове функции вычисления значений и побочные эффекты инициализации каждого параметра неопределённо упорядочены относительно вычислений значений и побочных эффектов любого другого параметра.
15) Каждый перегруженный оператор подчиняется правилам упорядочения встроенного оператора, который он перегружает, при вызове с использованием операторной нотации.
16) В выражении индексации E1 [ E2 ] , E1 упорядочено перед E2 .
17) В выражении указателя на член E1. * E2 или E1 - > * E2 , E1 упорядочено перед E2 (если только динамический тип E1 не содержит члена, на который ссылается E2 ).
18) В выражении оператора сдвига E1 << E2 и E1 >> E2 , E1 упорядочено перед E2 .
19) В каждом выражении простого присваивания E1 = E2 и каждом выражении составного присваивания E1 @ = E2 , E2 упорядочено перед E1 .
20) Каждое выражение в списке выражений, разделённых запятыми, в круглых скобках инициализатора вычисляется как при вызове функции (неопределённо упорядочено).
(since C++17)

Неопределённое поведение

Поведение является неопределённым в следующих случаях:

1) Побочный эффект на область памяти неупорядочен относительно другого побочного эффекта на ту же область памяти:
i = ++i + 2;       // корректно определено
i = i++ + 2;       // неопределенное поведение до C++17
f(i = -2, i = -2); // неопределенное поведение до C++17
f(++i, ++i);       // неопределенное поведение до C++17, неуточненное после C++17
i = ++i + i++;     // неопределенное поведение
2) Побочный эффект на область памяти неупорядочен относительно вычисления значения, использующего значение любого объекта в той же области памяти:
cout << i << i++; // undefined behavior until C++17
a[i] = i++;       // undefined behavior until C++17
n = ++i + i;      // undefined behavior
3) Начало или завершение времени жизни объекта в области памяти неупорядочено относительно любой из следующих операций:
  • побочного эффекта на той же области памяти
  • вычисления значения с использованием значения любого объекта в той же области памяти
  • начала или завершения времени жизни объекта, занимающего перекрывающуюся область памяти
union U { int x, y; } u;
(u.x = 1, 0) + (u.y = 2, 0); // undefined behavior

Правила точек следования (до C++11)

Определения до C++11

Вычисление выражения может вызывать побочные эффекты, которые включают: доступ к объекту, обозначенному через volatile lvalue, модификацию объекта, вызов библиотечной I/O функции или вызов функции, выполняющей любую из этих операций.

Точка следования — это точка в последовательности выполнения, в которой все побочные эффекты от предыдущих вычислений в последовательности завершены, и никакие побочные эффекты последующих вычислений еще не начались.

Правила до C++11

1) Точка следования имеется в конце каждого полного выражения (обычно на точке с запятой).
2) При вызове функции (независимо от того, является ли функция встроенной и использовался ли синтаксис вызова функции), существует точка следования после вычисления всех аргументов функции (если они есть), которая происходит до выполнения любых выражений или операторов в теле функции.
3) При возврате из функции точка следования возникает после копирующей инициализации результата вызова функции и до уничтожения всех временных объектов в конце expression в return statement (если применимо).
4) Существует точка следования после копирования возвращаемого значения функции и перед выполнением любых выражений вне функции.
5) После начала выполнения функции никакие выражения из вызывающей функции не вычисляются до завершения выполнения вызванной функции (функции не могут чередоваться).
6) При вычислении каждого из следующих четырех выражений, с использованием встроенных (не перегруженных) операторов, имеется точка следования после вычисления выражения a .
a && b
a || b
a ? b : c
a , b

Неопределенное поведение до C++11

Поведение является неопределённым в следующих случаях:

1) Между предыдущей и следующей точкой следования значение любого объекта в ячейке памяти модифицируется более одного раза при вычислении выражения:
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
2) Между предыдущей и следующей точкой следования, для объекта, значение которого изменяется вычислением выражения, его предыдущее значение используется способом, отличным от определения значения для сохранения:
cout << i << i++; // undefined behavior
a[i] = i++;       // undefined behavior

Отчеты о дефектах

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

DR Применяется к Поведение в опубликованной версии Корректное поведение
CWG 1885 C++11 последовательность разрушения автоматических
переменных при возврате из функции не была явной
добавлены правила последовательности
CWG 1949 C++11 использовалось "sequenced after", но не было определено в стандарте C++ определено как обратное
к "sequenced before"
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 Логический оператор AND [expr.log.and]
  • 7.6.15 Логический оператор OR [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 Логический оператор AND [expr.log.and]
  • 7.6.15 Логический оператор OR [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 Логический оператор AND [expr.log.and]
  • 8.15 Логический оператор OR [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 Логический оператор AND [expr.log.and]
  • 5.15 Логический оператор OR [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 Логический оператор AND [expr.log.and]
  • 5.15 Логический оператор OR [expr.log.or]
  • 5.16 Условный оператор [expr.cond]
  • 5.17 Операторы присваивания и составного присваивания [expr.ass]
  • 5.18 Оператор запятая [expr.comma]
  • 8.5.4 Списковая инициализация [dcl.init.list]

Смотрите также

C documentation для Order of evaluation