Особенности объявление переменных в блоках C и Objective-C
У меня хобби - в выходные я программирую для собственного развлечения. Вчера впервые за девять лет знакомства с Си-подобными языками столкнулся с проблемой, которая оказалась весьма курьезной: внутри блока switch сразу после case я не смог объявить переменную без ошибки компилятора. Подробности этой полумистической истории представлены ниже.
Слегка уточню, я писал код на Objective-C, поэтому пример будет с применением синтаксиса этого языка, однако описанная особенность также характерна как минимум для C и C#.
Суть проблемы
Имеется оператор switch вида:
Компилятор при этом подсвечивает третью строку, где содержится ошибка. В частности Xcode говорит нам, что Expected expression
.
Решение проблемы
Для тех, кому надо решить проблему сразу - два варианта решения.
Вариант 1. Поставить после case 1:
точку с запятой ;
:
Вариант 2. Заключить содержимое case’a в фигурные скобки:
Вариант 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:
Факт 2. Применять описанный ранее способ решения проблемы под номером 1 (вставка ;
) нельзя в случае объявления массива переменной длины - в C99
это variable length array
, например void *ptrs[n]
. Ошибки компилятора не вызовет только вариант case 1: { void *ptrs[count]; }
.