Spec-Zone.ru › C++

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

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

В 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)

Правила

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

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

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

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
3) Начало или завершение времени жизни объекта в области памяти не упорядочено относительно любой из следующих операций:
  • побочный эффект над той же областью памяти
  • вычисление значения, использующее значение любого объекта в той же области памяти
  • начало или завершение времени жизни объекта, занимающего хранилище, которое пересекается с этой областью памяти
union U { int x, y; } u;
(u.x = 1, 0) + (u.y = 2, 0); // 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++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

Spec-Zone.ru

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