Spec-Zone.ru › C++

Порядок вычисления

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

В C++ нет понятия вычисления слева направо или справа налево. Это не следует путать с ассоциативностью операторов слева направо и справа налево: выражение a() + b() + c() разбирается как (a() + b()) + c() из-за левой ассоциативности оператора +, но 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)

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

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

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

Порядок

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

  • Если 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. Порядок может быть обратным при следующем вычислении того же выражения.

Правила

1) Каждое вычисление значения и побочный эффект полного выражения полного выражения упорядочено до каждого вычисления значения и побочного эффекта следующего полного выражения.
2) Вычисления значений (но не побочные эффекты) операндов любого оператора упорядочены до вычисления значения результата оператора (но не его побочных эффектов).
3) При вызове функции (независимо от того, является ли функция встроенной и используется ли явная синтаксическая запись вызова функции), каждое вычисление значения и побочный эффект, связанный с любым выражением аргумента или с постфиксным выражением, обозначающим вызываемую функцию, упорядочено до выполнения каждого выражения или оператора в теле вызываемой функции.
4) Вычисление значения встроенных операторов пост-инкремента и пост-декремента упорядочено до его побочного эффекта.
5) Побочный эффект встроенных операторов префиксного инкремента и префиксного декремента упорядочен до его вычисления значения (неявленное правило из-за определения как составного присваивания).
6) Каждое вычисление значения и побочный эффект первого (левого) аргумента встроенного оператора логического И &&, встроенного логического ИЛИ оператора || и встроенного оператора запятой , упорядочены до каждого вычисления значения и побочного эффекта второго (правого) аргумента.
7) Каждое вычисление значения и побочный эффект первого выражения в условном операторе ?: упорядочены до каждого вычисления значения и побочного эффекта второго или третьего выражения.
8) Побочный эффект (изменение левого аргумента) встроенного оператора присваивания и всех встроенных составных операторов присваивания упорядочен после вычисления значения (но не побочных эффектов) как левого, так и правого аргументов и упорядочен до вычисления значения выражения присваивания (то есть, до возвращения ссылки на изменённый объект).
9) При инициализации списком инициализации списком, каждое вычисление значения и побочный эффект данного начального значения упорядочены до каждого вычисления значения и побочного эффекта, связанных с любым начальным значением, которое следует за ним в заключённом в фигурные скобки списке начальных значений, разделённых запятыми.
10) Вызов функции, который не упорядочен до или упорядочен после другого вычисления выражения вне функции (возможно, другой вызов функции), неопределённо упорядочен по отношению к этому вычислению (программа должна вести себя как если бы инструкции процессора, составляющие вызов функции, не чередовались с инструкциями, составляющими вычисления других выражений, включая другие вызовы функций, даже если функция была встроена).
Правило 10 имеет одно исключение: вызовы функций, сделанные алгоритмом стандартной библиотеки, выполняющимся под std::execution::par_unseq политикой выполнения, неупорядочены и могут произвольно чередоваться друг с другом. (с C++17)
11) Вызов функции выделения памяти (operator new) неопределённо упорядочен по отношению к(до C++17)упорядочен до(с C++17) вычисления аргументов конструктора в выражении new.
12) При возвращении из функции копирующая инициализация временного объекта, являющегося результатом вычисления вызова функции, упорядочена до уничтожения всех временных объектов в конце операнда оператора возврата, который, в свою очередь, упорядочен до уничтожения локальных переменных блока, содержащего оператор возврата.
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) Каждое выражение в списке выражений, разделённых запятыми, в заключённом в скобки списке инициализаторов, вычисляется так, как если бы это был вызов функции (неопределённо упорядочен). (с C++17)

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

1) Если побочный эффект на местоположение памяти неупорядочен по отношению к другому побочному эффекту на том же местеположение памяти, поведение является неопределённым.

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

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

cout << i << i++; // undefined behavior until C++17
a[i] = i++;       // undefined behavior until C++17
n = ++i + i;      // undefined behavior

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

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

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

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

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

1) Точка последовательности находится в конце каждого полного выражения (как правило, после точки с запятой).

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

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

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++98 "sequenced after" использовалось, но не было определено в стандарте C++ определено как обратное
"sequenced before"
CWG 2146 C++98 случаи, связанные с неопределённым поведением, не рассматривали битовые поля рассмотрены

Ссылки

  • Стандарт C++23 (ISO/IEC 14882:2023):
    • 6.9.1 Выполнение программы [intro.execution]
    • 7.6.1.6 Инкремент и декремент [expr.post.incr]
    • 7.6.2.8 Новый [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 Новый [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 Новый [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 Новый [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 Новый [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/w/cpp/language/eval_order

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API