Точка следования
Точка следования (англ. sequence point) — в программировании любая точка программы, в которой гарантируется, что все побочные эффекты предыдущих вычислений уже проявились, а побочные эффекты последующих ещё отсутствуют.
Точки следования часто упоминают, когда речь идёт о языках C и C++. В этих языках можно записать выражение, порядок вычисления подвыражений которого не определён стандартами и влияет на результат. Добавление одной или нескольких точек следования позволяет гарантировать порядок вычисления в некоторых случаях.
Стоит заметить, что подход к упорядочению процесса вычисления выражений на основе точек следования изначально достаточно хорошо отвечал потребностям языка C, но не являлся адекватным для языка C++, в котором был существенно расширен набор операторов, возвращающих lvalue результаты. А с появлением необходимости языковой поддержки многопоточности в языках C и C++ от упорядочения на основе точек следования пришлось отказаться полностью. Современные спецификации языков C и C++ описывают упорядочение процесса вычисления выражений через отношения упорядочено до (sequenced before) и упорядочено после (sequenced after). Начиная со стандарта C++11, в языке C++ больше не существует понятия точки следования. В языке С понятие точки следования сохранилось по сей день, но, начиная со стандарта C11, не как фундаментальная концепция, а лишь как комбинация отношений упорядочено до и упорядочено после.
Стандарт C++11, а также последующие стандарты C++14 и C++17 внесли в операторы языка C++ большое количество дополнительных упорядочений на основе новой модели, что привело к тому, что многие выражения, поведение которых являлось неопределенным в C++98, получили вполне определённое поведение в современном C++. На сегодняшний день строгость упорядочения процесса вычисления выражений в языке C++ существенно превосходит таковую в языке C.
Примеры неоднозначности в языках C и C++
При наличии неоднозначностей стандарты языков C и C++:
- указывают несколько допустимых поведений из числа возможных (см. неуточняемое поведение);
- указывают единственно допустимое поведение из числа возможных либо
- явно указывают, что поведение не определено (см. неопределённое поведение).
Пример 1. Неуточняемое поведение.
g() + f()
Оператор «+
» не является точкой следования, поэтому неизвестно, какая из функций будет вызвана первой: f()
или g()
. Поведение зависит от реализации компилятора.
Пример 2. Единственно допустимое поведение.
f(), g()
Оператор «,
» является точкой следования, поэтому порядок вычисления гарантируется стандартом и известен заранее (слева направо):
- сначала вычисляется левый операнд: вызывается функция
f()
; - затем — правый: вызывается функция
g()
.
Пример 3. Неопределённое поведение.
i = ++i + i++
С точки зрения языка C указанное выражение содержит множественные модификации переменной i
, не упорядоченные относительно друг друга. Поведение данного выражения не определено. (В то же время с точки зрения современного языка C++, который существенно более строго упорядочивает процесс вычисления оператора присваивания, поведение этого выражения полностью определено.)
Точки следования в языках C и C++
В оригинальных стандартах языков C и C++ были определены следующие точки следования:
- точки следования для операторов «&&», «||» и «,». Эти операторы гарантированно вычисляются слева направо, если не перегружены. Пример. В выражении «
*p++ != 0 && *q++ != 0
» сначала вычисляется левый операнд («*p++ != 0
»); результат приводится к типуbool
и сравнивается сtrue
; если равенtrue
, вычисляется правый операнд («*q++ != 0
»), иначе возвращаетсяfalse
;
- точка следования для тернарного оператора «?:». 1-й операнд вычисляется первым; затем располагается точка следования; 2-й операнд вычисляется только, если 1-й операнд равен
true
; 3-й операнд вычисляется только, если 1-й операнд равенfalse
. Пример. В выражении «a == (*p++) ? (*p++) : 0
» сначала выполняется 1-й операнд («*p++
»; переменнаяp
увеличивается на1
); результат вычисления приводится к типуbool
и сравнивается сtrue
; если равенtrue
, выполняется 2-й операнд («(*p++)
»), иначе — 3-й («0»);
- точки следования в выражениях:
- на месте символа «
;
» в выражениях, являющихся отдельными инструкциями. Например, в выражении «a = b;
» точка следования вставляется вместо «;
»; - в конце выражения, записанного после ключевого слова
return
; а точнее, на момент, когда возвращаемое значение будет скопировано в контекст вызывающей функции. Эта точка следования явно описана только в стандарте C++; - в конце выражений, записанных в круглых скобках после ключевых слов
if
,switch
,while
(включаяwhile
в конструкцииdo-while
); - в концах каждого из трёх выражений для цикла
for
;
- на месте символа «
- перед вызовом функции. Порядок вычисления аргументов функции не определён. Точка следования гарантирует, что все аргументы будут вычислены до вызова функции. Пример. Рассмотрим выражение «
f( i++ ) + g( j++ ) + h( k++ )
». Сначала создаётся временная переменная со значением, равным значению переменнойi
; затем для переменнойi
(не для временной) вызывается оператор «постфиксный ++»; наконец, вызывается функцияf()
с временной переменной в качестве аргумента. Сказанное справедливо для переменныхj
,k
и функцийg()
,h()
соответственно. При этом из-за отсутствия точки следования у оператора «+» порядок вызова функцийf()
,g()
иh()
не определён. Следовательно не определён и порядок вызова операторов «постфиксный ++» для переменныхi
,j
иk
. То есть при выполнении функцииf()
неизвестно, были ли вызваны операторы «постфиксный ++» для переменныхj
иk
. Пример. Рассмотрим выражение «f( a, b, c )
». Запятая между аргументами функции не является оператором «запятая» и не гарантирует порядок вычисления значений аргументов. Порядок вычисления значений аргументов функции не стандартизован и зависит от реализации компилятора;
- в объявлении с инициализацией на момент завершения вычисления инициализирующего значения. Пример. Рассмотрим выражение «
int a = ( 1 + i++ );
». Точка следования вставляется после вычисления выражения «( 1 + i++ )
»;
- перед вызовом перегруженного оператора в языке C++. Точка следования гарантирует вычисление значений аргументов оператора (как и обычной функции) до его вызова.
См. также
Примечания
Ссылки
- Точки следования (sequence points)
- Клаус Крэфт, Анжелика Лангер. Точки следования и вычисление выражений в C++
- cppreference.com. Порядок вычислений в C (англ.) и C++ (англ.)