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

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

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

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

В C++ не существует понятия вычисления слева направо или справа налево, что не нужно путать с левосторонней (left-to-right) или правосторонней (right-to-left) ассоциативностью операторов: выражение f1() + f2() + f3() разбирается как (f1() + f2()) + f3() за счёт левоассоциативности operator+, но вызов функции f3 может быть вычислен первым, последним, или между f1() и f2() во время выполнения.

Содержание

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

[править] Определения

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

Есть два вида вычислений выполняемых компилятором для каждого выражения или вложенного выражения (оба из которых не являются обязательными):

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

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

"Расположено-перед" - это ассимитричное, транзитивное, попарное отношение между вычислениями в пределах одного потока (оно может распостраняться между потоками, если атомарные типы задействованы с подходящим упорядочение памяти std::memory_order).

  • Если 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) Каждое вычисление значения и побочный эффект полного выражения (выражения, не являющегося частью другого выражения, обычно заканчивающегося точкой с запятой) расположены-перед вычислением каждого значения и побочным эффектом следующего полного выражения.

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

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

4) Вычисление значения встроенного оператора постфиксного инкремента и встроенного оператора постфиксного декремента расположено-перед их побочными эффектами.

5) Побочный эффект встроенного оператора префиксного инкремента и встроенного оператора префиксного декремента расположен-перед вычислением его значения (неявное правило, в связи с тем, что встроенные префиксные операторы определены как составное присваивание).

6) Каждые вычисление значения и побочный эффект первого (стоящего слева) аргумента встроенного логического И (operator &&) и встроенного логического ИЛИ (operator ||) расположены-перед каждым вычислением значения и побочного эффекта второго (стоящего справа) аргумента.

7) Каждое вычисление значения и побочный эффект связанные с первым вычислением в логическом ?: операторе расположены-перед любым вычислением значения и побочным эффектом, связанным со вторым или третьим выражением.

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

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

10) В Списке инициализации, каждое вычисление значения и побочный эффект данного элемента инициализатора расположены-перед каждым вычислением значения и побочным эффектом связанным с любым элементом инициализатора, который следует за данным, в списке инициализации, разделённом запятыми.

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

12) Вызов функции распределения (operator new) расположен-неопределённо по отношению к вычислению аргументов конструктора в new-expression.

[править] Неопределенное поведение

1) Если влияние побочного эффекта на скалярный объект не упорядочено по отношению к другому побочному эффекту, действующему на тот же скалярный объект - поведение не определено (behavior is undefined).

i = ++i + i++; // undefined behavior (неопределённое поведение)
i = i++ + 1; // undefined behavior (но i = ++i + 1; является well-defined)
f(++i, ++i); // undefined behavior
f(i = -1, i = -1); // undefined behavior

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

cout << i << i++; // undefined behavior
a[i] = i++; // undefined bevahior

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

[править] Определения

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

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

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

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

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

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

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

5) При вычислении каждого из следующих четырёх выражений, использующих встроенные (не перегруженные) операторы, точка следования находится после вычисления выражения a.

a && b
a || b
a ? b : c
a , b

[править] Неопределенное поведение

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

i = ++i + i++; // undefined behavior (неопределённое поведение)
i = i++ + 1; // undefined behavior
i = ++i + 1; // undefined behavior (well-defined в C++11)
++ ++i; // undefined behavior (well-defined в C++11)
f(++i, ++i); // undefined behavior
f(i = -1, i = -1); // undefined behavior

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

cout << i << i++; // undefined behavior
a[i] = i++; // undefined bevahior

[править] См. также

  • Приоритет операторов, который определяет, как выражения выстраиваются из их представления в исходном коде.