Пространства имён
Варианты
Действия

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

Материал из cppreference.com
< cpp‎ | language
 
 
Язык С++
Общие темы
Управление потоком
Операторы условного выполнения
if
Операторы итерации (циклы)
Операторы перехода
Функции
Объявление функции
Выражение лямбда-функции
Спецификатор inline
Спецификации динамических исключений (до C++17*)
Спецификатор noexcept (C++11)
Исключения
Пространства имён
Типы
Спецификаторы
decltype (C++11)
auto (C++11)
alignas (C++11)
Спецификаторы длительности хранения
Инициализация
Выражения
Категории значений
Порядок оценки
Альтернативные представления
Литералы
Логические - Целочисленные - С плавающей запятой
Символьные - Строковые - nullptr (C++11)
Определяемые пользователем (C++11)
Утилиты
Атрибуты (C++11)
Types
Объявление typedef
Объявление псевдонима типа (C++11)
Casts
Неявные преобразования - Явные преобразования
static_cast - dynamic_cast
const_cast - reinterpret_cast
Выделение памяти
Классы
Свойства функции класса
explicit (C++11)
static
Специальные функции-элементы
Шаблоны
Разное
 

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

В 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


Содержание

[править] Правила "расположено-перед" (начиная с C++11)

[править] Вычисление Выражений

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

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

[править] Упорядочение

"Расположено-перед" это ассимитричное, транзитивное, попарное отношение между вычислениями в одном потоке.

  • Если A расположено-перед B, тогда вычисление A будет завершено до того, как начнётся вычисление B.
  • Если A не расположено-перед B и B расположено-перед A, тогда вычисление B будет завершено до того, как начнётся вычисление A.
  • Если A не расположено-перед B и B не расположено-перед A, тогда существуют две возможности:
    • Вычисления A и B неупорядочены: они могут быть выполнены в любом порядке, и могут перекрываться (в пределах одного потока выполнения компилятор может чередовать инструкции ЦП, выполняемые для вычисления A и B).
    • Вычисления A и B имеют неопределённую последовательность: они могут быть выполнены в любом порядке, но не могут перекрываться: либо A будет вычислено перед B, либо B будет вычислено перед A. При следующем вычислении этого же выражения порядок может быть противоположным.

[править] Правила

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

[править] Правила точки следования (до 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++;     // неопределённое поведение
i = i++ + 1;       // неопределённое поведение
i = ++i + 1;       // неопределённое поведение
++ ++i;            // неопределённое поведение
f(++i, ++i);       // неопределённое поведение
f(i = -1, i = -1); // неопределённое поведение

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

cout << i << i++; // неопределённое поведение
a[i] = i++;       // неопределённое поведение

[править] Отчёты о дефектах

Следующие изменения поведения были применены с обратной силой к ранее опубликованным стандартам C++:

Номер Применён Поведение в стандарте Корректное поведение
CWG 1885 C++11 последовательность уничт��жения автоматических переменных
при возврате из функции не была явной
добавлены правила следования
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 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 по Порядок вычисления