У меня хобби - в выходные я программирую для собственного развлечения. Вчера впервые за девять лет знакомства с Си-подобными языками столкнулся с проблемой, которая оказалась весьма курьезной: внутри блока switch сразу после case я не смог объявить переменную без ошибки компилятора. Подробности этой полумистической истории представлены ниже.

Слегка уточню, я писал код на Objective-C, поэтому пример будет с применением синтаксиса этого языка, однако описанная особенность также характерна как минимум для C и C#.

Суть проблемы

Имеется оператор switch вида:

switch ([self something]) {
  case 1:
    int i;
    break;
  default:
    break;
}

Компилятор при этом подсвечивает третью строку, где содержится ошибка. В частности Xcode говорит нам, что Expected expression.

Решение проблемы

Для тех, кому надо решить проблему сразу - два варианта решения. Вариант 1. Поставить после case 1: точку с запятой ;:

switch ([self something]) {
  case 1:;
    int i;
    break;
  default:
    break;
}

Вариант 2. Заключить содержимое case’a в фигурные скобки:

switch ([self something]) {
  case 1:
  {
    int i;
    break;
  }
  default:
    break;
}

Вариант 2 является более предпочтительным, чем Вариант 1.

Почему это происходит?

Небольшое исследование привело меня к следующему объяснению: до стандарта C99 объявлять локальные переменные внутри блока можно было только в начале этого блока, после чего шли операторы (statements). Начиная с C99 можно было мешать объявления переменных и операторы в любом порядке, как вздумается.

В терминах BNF-грамматики C99 объявление переменной называется declaration, а оператор обозначается как statement. При этом statement может представлять собой составной оператор - compound-statement, который создается заключением группы операторов в фигурные скобки. Внутри фигурных скобок находятся 0 и более block-items, которые представляют собой либо declaration, либо statement.

Проблема находится в определении в BNF-грамматике C99 так называемых именованных операторов - labeled-statement (goto label, case label, default:, любом другом операторе вида ...:), которое выглядит как ...: 0 или более statements. Обратите внимание, это не 0 или более statements или declarations! Таким образом, для того, чтобы вставить после labeled-statement объявление переменной declaration необходимо в качестве statement сразу после labeled-statement использовать составной оператор compound-statement. Добиться этого можно одним из описанных в предыдущем подразделе способов.

Любопытные факты

Факт 1. Что любопытно, такая же проблема возникает при попытки объявить переменную сразу после for, while, do, а также после условий if и else. Остроумный пример багованного кода дает пользователь stackoverflow Quinn Taylor:

if (1)
    int i;
else
    int i;
for (int answer = 1; answer <= 42; answer ++)
    int i;
while (1)
    int i;
do
    int i;
while (1);

Факт 2. Применять описанный ранее способ решения проблемы под номером 1 (вставка ;) нельзя в случае объявления массива переменной длины - в C99 это variable length array, например void *ptrs[n]. Ошибки компилятора не вызовет только вариант case 1: { void *ptrs[count]; }.