Конечные точки и достижимость
У каждого оператора языка имеется конечная точка. Интуитивно говоря, конечная точка оператора — это позиция, непосредственно следующая за оператором. Правилами выполнения составных операторов (операторов языка, содержащих внедренные операторы) определяется действие, которое предпринимается, когда управление достигает конечной точки внедренного оператора. Например, когда управление достигает конечной точки оператора в блоке, оно передается следующему оператору этого блока.
Если существует возможность передачи управления оператору языка в ходе выполнения, говорят, что он является достижимым. И наоборот, если возможность выполнения оператора исключена, он называется недостижимым.
В этом примере
void F() {
Console.WriteLine("reachable");
goto Label;
Console.WriteLine("unreachable");
Label:
Console.WriteLine("reachable");
}
второй вызов функции Console.WriteLine недостижим, потому что он не может быть выполнен ни при каких условиях.
Если компилятором установлен факт недостижимости оператора языка, выдается предупреждение. Недостижимость оператора не рассматривается как ошибка.
Чтобы определить, достижим ли данный оператор языка или конечная точка, компилятор выполняет анализ потока управления в соответствии с правилами достижимости, установленными для каждого оператора. В ходе анализа принимаются во внимание значения константных выражений (§7.19), контролирующих поведение операторов, но возможные значения неконстантных выражений не учитываются. Иными словами, при анализе потока управления считается, что неконстантное выражение данного типа может принимать любое значение этого типа.
В этом примере
void F() {
const int i = 1;
if (i == 2) Console.WriteLine("unreachable");
}
логическое выражение оператора if является константным выражением, поскольку оба операнда оператора == представляют собой константы. Поскольку константное выражение вычисляется во время компиляции и его значением оказывается false, вызов Console.WriteLine считается недостижимым. Однако если переменную i сделать локальной:
void F() {
int i = 1;
if (i == 2) Console.WriteLine("reachable");
}
то вызов Console.WriteLine станет достижимым, хотя на самом деле и не будет никогда выполняться.
Блок члена-функции всегда считается достижимым. Последовательно применяя правила достижимости каждого оператора блока, можно определить достижимость любого конкретного оператора языка.
В этом примере
void F(int x) {
Console.WriteLine("start");
if (x < 0) Console.WriteLine("negative");
}
доступность второго вызова Console.WriteLine определяется следующим образом.
· Первый оператор-выражение Console.WriteLine доступен, поскольку доступен блок метода F.
· Конечная точка первого оператора-выражения Console.WriteLine достижима, поскольку сам оператор достижим.
· Оператор if доступен, поскольку доступна конечная точка первого оператора-выражения Console.WriteLine.
· Второй оператор-выражение Console.WriteLine доступен, поскольку значением логического выражения оператора if не является константа false.
В двух ситуациях достижимость конечной точки оператора языка означает ошибку компиляции.
· Поскольку оператор switch не позволяет перейти из одного раздела switch в следующий, достижимость конечной точки списка операторов раздела switch распознается как ошибка компиляции. Обычно такая ошибка свидетельствует об отсутствии оператора break.
· Достижимость конечной точки блока функции-члена, вычисляющего значение, распознается как ошибка компиляции. Обычно такая ошибка свидетельствует об отсутствии оператора return.
Блоки
Блок позволяет записать несколько операторов языка в контексте, обычно допускающем использование только одного оператора.
block:
{ statement-listopt }
Блок состоит из необязательного списка операторов (§8.2.1), заключенного в фигурные скобки. Если список операторов опущен, говорят, что блок пустой.
Блок может включать операторы объявления (§8.5). Областью видимости локальной переменной или константы, объявленной в блоке, является этот блок.
Значение имени, используемого в контексте выражения, всегда должно быть одинаковым в пределах блока (§7.6.2.1).
Блок выполняется следующим образом.
· Если блок пустой, управление передается в конечную точку блока.
· Если блок непустой, управление передается в список операторов. Если управление достигает конечной точки списка операторов, после этого управление передается в конечную точку блока.
Список операторов блока считается достижимым, если сам блок является достижимым.
Конечная точка блока достижима, если блок пустой или если конечная точка списка операторов достижима.
Блок, содержащий один или несколько операторов yield (§8.14), называется блоком итератора. Блоки итераторов используются для реализации функций-членов в виде итераторов (§10.14). В отношении блоков итераторов действуют дополнительные ограничения.
· Появление оператора return в блоке итератора приводит к ошибке во время компиляции (но при этом операторы yield return разрешены).
· Наличие небезопасного контекста в блоке итератора распознается как ошибка времени компиляции (§18.1). Блок итератора всегда определяет безопасный контекст, даже если его объявление вложено в небезопасный контекст.
Списки операторов
Список операторов состоит из одного или нескольких последовательно записанных операторов языка. Списки операторов могут входить в блоки (§8.2) и в блоки switch (§8.7.2).
statement-list:
statement
statement-list statement
Выполнение списка операторов начинается с передачи управления первому оператору. Если управление достигает конечной точки оператора, после этого управление передается следующему оператору. Если управление достигает конечной точки последнего оператора, после этого управление передается в конечную точку списка операторов.
Оператор списка считается достижимым, если соблюдено по крайней мере одно из следующих условий.
· Оператор является первым в списке и сам список операторов является достижимым.
· Конечная точка предыдущего оператора достижима.
· Оператор является помеченным и его метка указывается в достижимом операторе goto.
Конечная точка списка операторов достижима, если достижима конечная точка последнего оператора языка в списке.
Пустой оператор
Пустой оператор не выполняет никаких действий.
empty-statement:
;
Пустой оператор используется, когда в контексте, требующем наличия оператора языка, не требуется выполнять никаких операций.
Выполнение пустого оператора сводится к передаче управления в конечную точку оператора. Таким образом, конечная точка пустого оператора достижима, если достижим сам пустой оператор.
Пустой оператор может использоваться при записи оператора while с пустым телом:
bool ProcessMessage() {...}
void ProcessMessages() {
while (ProcessMessage())
;
}
Кроме того, с помощью пустого оператора можно объявить метку непосредственно перед закрывающей фигурной скобкой (}) блока:
void F() {
...
if (done) goto exit;
...
exit: ;
}
Помеченные операторы
Помеченный оператор позволяет предварить оператор языка меткой. Помеченные операторы разрешается использовать в блоках, но запрещается использовать как внедренные операторы.
labeled-statement:
identifier : statement
Помеченный оператор объявляет метку, имя которой задает идентификатор. Областью видимости метки является весь блок, в котором она объявлена, включая вложенные блоки, если они имеются. Если у двух меток с одним именем перекрывающиеся области видимости, это распознается как ошибка времени компиляции.
Метка может указываться в операторах goto (§8.9.3), находящихся в области ее видимости. Это означает, что оператор goto может передавать управление в пределах блока и за его пределы, но не внутрь блока.
Метки имеют собственную область объявления и не вступают в конфликт с другими идентификаторами. Пример:
int F(int x) {
if (x >= 0) goto x;
x = -x;
x: return x;
}
является допустимым; в нем имя x используется и как параметр, и как метка.
Выполнение помеченного оператора происходит точно так же, как выполнение оператора языка, следующего за меткой.
Помимо достижимости в рамках обычного потока управления, помеченный оператор может быть достижимым в случае, если его метка указывается в достижимом операторе goto. (Имеется исключение: если оператор goto находится внутри оператора try, включающего блок finally, а помеченный оператор находится вне оператора try и при этом конечная точка блока finally недоступна, то помеченный оператор недоступен из такого оператора goto.)
Операторы объявления
Оператор объявления объявляет локальную переменную или константу. Операторы объявления разрешается использовать в блоках, но запрещается использовать как внедренные операторы.
declaration-statement:
local-variable-declaration ;
local-constant-declaration ;