Особенности объявление переменных в блоках C и Objective-C
У меня хобби - в выходные я программирую для собственного развлечения. Вчера впервые за девять лет знакомства с Си-подобными языками столкнулся с проблемой, которая оказалась весьма курьезной: внутри блока 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]; }.