Order of evaluation
Порядок вычисления любой части любого выражения, включая порядок вычисления аргументов функции, является неопределённым (за некоторыми исключениями, перечисленными ниже). Компилятор может вычислять операнды и другие подвыражения в любом порядке и может выбрать другой порядок при повторном вычислении того же выражения.
В C++ не существует концепции вычисления слева направо или справа налево. Это не следует путать с левоассоциативностью и правоассоциативностью операторов: выражение a ( ) + b ( ) + c ( ) парсится как ( a ( ) + b ( ) ) + c ( ) благодаря левоассоциативности operator + , но c ( ) может быть вычислен первым, последним или между a ( ) и b ( ) во время выполнения:
Возможный вывод:
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 .
Правила
- каждое выражение аргумента и постфиксное выражение, обозначающее func
|
(начиная с C++26) |
- каждое выражение или оператор в теле функции func
|
(начиная с C++26) |
| Правило 10 имеет одно исключение: вызовы функций, выполняемые алгоритмом стандартной библиотеки с политикой выполнения std::execution::par_unseq , являются неупорядоченными и могут произвольно перемежаться друг с другом. | (начиная с C++17) |
|
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) |
Неопределённое поведение
Поведение является неопределённым в следующих случаях:
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++; // неопределенное поведение
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, модификацию объекта, вызов библиотечной I/O функции или вызов функции, выполняющей любую из этих операций.
Точка следования — это точка в последовательности выполнения, в которой все побочные эффекты от предыдущих вычислений в последовательности завершены, и никакие побочные эффекты последующих вычислений еще не начались.
Правила до C++11
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 | использовалось "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
|