Точка последовательности
В C и C++ точка последовательности определяет любую точку компьютерной программы , выполнения в которой гарантируется, что все побочные эффекты предыдущих вычислений будут выполнены, и никакие побочные эффекты последующих вычислений еще не были выполнены. Они являются основной концепцией для определения достоверности и, если допустимы, возможных результатов выражений. Добавление дополнительных точек последовательности иногда необходимо, чтобы определить выражение и обеспечить единый допустимый порядок вычислений.
В C11 и C++11 использование термина «точка последовательности» было заменено на «упорядочение». Есть три возможности: [1] [2] [3]
- Вычисление выражения может быть упорядочено перед вычислением другого выражения или, что эквивалентно, вычисление другого выражения может быть расположено после вычисления первого.
- Вычисление выражений имеет неопределенную последовательность, то есть одно упорядочивается перед другим, но какое именно значение не указано.
- Вычисление выражений не является последовательным.
Выполнение неупорядоченных оценок может перекрываться, что приводит к потенциально катастрофическому неопределенному поведению, если они имеют общее состояние . Такая ситуация может возникнуть при параллельных вычислениях , вызывая условия гонки , но неопределенное поведение также может привести к однопоточным ситуациям. Например, a[i] = i++;
(где a
представляет собой массив и i
является целым числом) имеет неопределенное поведение.
Примеры двусмысленности [ править ]
Рассмотрим две функции f()
и g()
. В C и C++ +
оператор не связан с точкой последовательности, поэтому в выражении f()+g()
возможно, что либо f()
или g()
будет выполнен первым. Оператор запятая вводит точку последовательности, и поэтому в коде f(),g()
определен порядок оценки: сначала f()
называется, а затем g()
называется.
Точки последовательности также вступают в игру, когда одна и та же переменная изменяется более одного раза в одном выражении. Часто цитируемым примером является C выражение i=i++
, что, по-видимому, оба присваивает i
его предыдущее значение и приращения i
. Окончательное значение i
неоднозначно, поскольку в зависимости от порядка вычисления выражения приращение может происходить до, после или чередоваться с присваиванием. Определение конкретного языка может указывать одно из возможных вариантов поведения или просто говорить, что поведение не определено . В C и C++ вычисление такого выражения приводит к неопределенному поведению. [4] Другие языки, такие как C# , определяют приоритет оператора присваивания и увеличения таким образом, что результат выражения i=i++
гарантировано.
Поведение [ править ]
До C++03 [ править ]
В С [5] и С++, [6] Точки последовательности встречаются в следующих местах. (В C++ перегруженные операторы действуют как функции, и поэтому перегруженные операторы вводят точки последовательности так же, как вызовы функций.)
- Между вычислением левого и правого операндов
&&
( логическое И ),||
( логическое ИЛИ ) (как часть сокращенной оценки ) и операторы-запятые . Например, в выражении*p++ != 0 && *q++ != 0
, все побочные эффекты подвыражения*p++ != 0
завершаются до любой попытки доступаq
. - Между оценкой первого операнда тернарного условного оператора и его второго или третьего операнда. Например, в выражении
a = (*p++) ? (*p++) : 0
после первого есть точка последовательности*p++
, что означает, что оно уже было увеличено к моменту выполнения второго экземпляра. - В конце полного выражения. В эту категорию входят операторы выражения (например, оператор присваивания
a=b;
), операторы возврата , управляющие выраженияif
,switch
,while
, илиdo
-while
утверждения, и каждое из трех выражений вfor
заявление. - Прежде чем функция будет введена в вызов функции. Порядок, в котором оцениваются аргументы, не указан, но эта точка последовательности означает, что все их побочные эффекты завершаются до входа в функцию. В выражении
f(i++) + g(j++) + h(k++)
,f
вызывается с параметром исходного значенияi
, ноi
увеличивается перед входом в телоf
. Сходным образом,j
иk
обновляются перед вводомg
иh
соответственно. Однако не уточняется, в каком порядкеf()
,g()
,h()
выполняются и в каком порядкеi
,j
,k
увеличиваются. Если телоf
обращается к переменнымj
иk
, он может обнаружить, что были увеличены оба значения, ни одно из них, или только одно из них. (вызов функцииf(a,b,c)
не используется оператор запятая; порядок оценкиa
,b
, иc
не указано.) - При возврате функции, после того как возвращаемое значение копируется в вызывающий контекст. (Эта точка последовательности указана только в стандарте C++; в C она присутствует только неявно. [7] )
- В конце инициализатора ; например, после оценки
5
в декларацииint a = 5;
. - Между каждым декларатором в каждой последовательности деклараторов; например, между двумя оценками
a++
вint x = a++, y = a++
. [8] (Это не пример оператора запятой.) - После каждого преобразования связан спецификатор формата ввода/вывода. Например, в выражении
printf("foo %n %d", &a, 42)
, после%n
оценивается и перед печатью42
.
C11 и C++11 [ править ]
Этот раздел нуждается в расширении . Вы можете помочь, добавив к нему . ( апрель 2023 г. ) |
Частично из-за введения языковой поддержки потоков в C11 и C++11 введена новая терминология для порядка вычислений. Одна операция может быть «последовательной» перед другой, либо обе операции могут быть «неопределенно» упорядоченными (одна должна завершиться раньше другой) или «неупорядоченными» (операции в каждом выражении могут чередоваться).
С++17 [ править ]
В C++17 ограничены некоторые аспекты порядка вычислений. new
выражение всегда будет выполнять выделение памяти перед оценкой аргументов конструктора. Операторы <<
, >>
, .
, .*
, ->*
, а индекс и оператор вызова функции гарантированно будут оцениваться слева направо (независимо от того, перегружены они или нет). Например, код
std::cout << a() << b() << c(); // parsed as (((std::cout << a()) << b()) << c());
недавно гарантированно позвонит a
, b
и c
в таком порядке. Правая часть любого оператора присваивания вычисляется раньше левой, так что b() *= a();
гарантированно оценит a
первый. Наконец, хотя порядок вычисления параметров функции остается определяемым реализацией, компилятору больше не разрешено чередовать подвыражения для нескольких параметров. [9]
См. также [ править ]
Ссылки [ править ]
- ^ «ИСО/МЭК 14882:2011» . Проверено 4 июля 2012 г.
- ^ «Более детальная альтернатива точкам последовательности (пересмотренная) (WG21/N2239 J16/07-0099)» . Проверено 5 июля 2012 г.
- ^ «Порядок оценки» . Проверено 14 октября 2015 г.
- ^ Пункт 6.5#2 спецификации C99 : «Между предыдущей и следующей точкой последовательности сохраненное значение объекта должно быть изменено не более одного раза путем оценки выражения. Кроме того, доступ к предыдущему значению должен быть доступен только для определения значения для храниться».
- ^ В приложении C спецификации C99 перечислены обстоятельства, при которых можно предположить точку последовательности.
- ^ В стандарте C++ 1998 года точки последовательности для этого языка перечислены в разделе 1.9, параграфы 16–18.
- ^ Стандарт C++, ISO 14882:2003, раздел 1.9, сноска 11.
- ^ Стандарт C++, ISO 14882:2003, раздел 8.3: «Каждый init-декларатор в объявлении анализируется отдельно, как если бы он находился в объявлении сам по себе».
- ^ Дос Рейс, Габриэль; Саттер, Херб; Кейвс, Джонатан (23 июня 2016 г.). «Уточнение порядка оценки выражений для идиоматического C++» (PDF) . open-std.org . стр. 1–5 . Проверено 28 апреля 2023 г.
Внешние ссылки [ править ]
- Вопрос 3.8 FAQ по comp.lang.c