Допустимый, но бесполезный синтаксис в switch-case?
Через небольшую опечатку я случайно нашел эту конструкцию:
int main(void) {
char foo = 'c';
switch(foo)
{
printf("Cant Touch This\n"); // This line is Unreachable
case 'a': printf("A\n"); break;
case 'b': printf("B\n"); break;
case 'c': printf("C\n"); break;
case 'd': printf("D\n"); break;
}
return 0;
}
Кажется, что printf
на вершине switch
Заявление является действительным, но также совершенно недостижимым.
Я получил чистую компиляцию, даже без предупреждения о недоступном коде, но это кажется бессмысленным.
Должен ли компилятор пометить это как недоступный код?
Это вообще служит какой-либо цели?
8 ответов
Возможно, не самый полезный, но не совсем бесполезный. Вы можете использовать его для объявления локальной переменной, доступной в switch
объем.
switch (foo)
{
int i;
case 0:
i = 0;
//....
case 1:
i = 1;
//....
}
Стандарт (N1579 6.8.4.2/7
) имеет следующий образец:
ПРИМЕР В фрагменте искусственной программы
switch (expr) { int i = 4; f(i); case 0: i = 17; /* falls through into default code */ default: printf("%d\n", i); }
объект, чей идентификатор
i
существует с автоматическим хранением (в пределах блока), но никогда не инициализируется, и, таким образом, если управляющее выражение имеет ненулевое значение, вызовprintf
Функция получит доступ к неопределенному значению. Точно так же вызов функцииf
не может быть достигнуто
PS Кстати, образец не является допустимым кодом C++. В таком случае (N4140 6.7/3
Акцент мой)
Программа, которая переходит на 90 градусов от точки, где переменная с автоматическим продолжением хранения не находится в области видимости, до точки, где она находится в области видимости, плохо сформирована, если переменная не имеет скалярного типа, типа класса с тривиальным конструктором по умолчанию и тривиальным деструктором, cv-квалифицированная версия одного из этих типов или массив одного из предыдущих типов и объявляется без инициализатора (8.5).
90) Перевод из состояния
switch
Заявление к метке кейса считается скачком в этом отношении.
Так что замена int i = 4;
с int i;
делает его действительным C++.
Это вообще служит какой-либо цели?
Да. Если вместо утверждения вы поместите объявление перед первым ярлыком, это может иметь смысл:
switch (a) {
int i;
case 0:
i = f(); g(); h(i);
break;
case 1:
i = g(); f(); h(i);
break;
}
Правила для объявлений и операторов являются общими для блоков в целом, так что это то же правило, которое допускает это, что также позволяет использовать операторы там.
Стоит также упомянуть, что если первый оператор является конструкцией цикла, в теле цикла могут появляться метки case:
switch (i) {
for (;;) {
f();
case 1:
g();
case 2:
if (h()) break;
}
}
Пожалуйста, не пишите такой код, если есть более читабельный способ его написания, но он вполне допустим, и f()
звонок доступен.
Есть известное использование этого устройства под названием Duff's.
int n = (count+3)/4;
switch (count % 4) {
do {
case 0: *to = *from++;
case 3: *to = *from++;
case 2: *to = *from++;
case 1: *to = *from++;
} while (--n > 0);
}
Здесь мы копируем буфер, на который указывает from
в буфер, на который указывает to
, Мы копируем count
экземпляры данных.
do{}while()
заявление начинается до первого case
ярлык, а case
ярлыки встроены в do{}while()
,
Это уменьшает количество условных ветвей в конце do{}while()
цикл встречается примерно в 4 раза (в этом примере константа может быть настроена на любое значение, которое вы хотите).
Теперь оптимизаторы могут иногда делать это для вас (особенно если они оптимизируют потоковые / векторизованные инструкции), но без оптимизации по профилю они не могут знать, ожидаете ли вы большой цикл или нет.
В общем, объявления переменных могут происходить там и использоваться в каждом случае, но выходить за рамки после завершения переключения. (обратите внимание, что любая инициализация будет пропущена)
Кроме того, поток управления, не зависящий от переключателя, может привести вас в этот раздел блока переключателей, как показано выше, или с помощью goto
,
Если вы используете gcc в Linux, вы получите предупреждение, если вы используете 4.4 или более раннюю версию.
Опция -Wunreachable-code была удалена в gcc 4.4 и более поздних версиях.
Не только для объявления переменных, но и для сложных прыжков. Вы можете использовать его хорошо, если и только если вы не склонны к спагетти-коду.
int main()
{
int i = 1;
switch(i)
{
nocase:
printf("no case\n");
case 0: printf("0\n"); break;
case 1: printf("1\n"); goto nocase;
}
return 0;
}
Печать
1
no case
0 /* Notice how "0" prints even though i = 1 */
Следует отметить, что распределительный шкаф является одним из самых быстрых условий потока управления. Таким образом, он должен быть очень гибким для программиста, что иногда включает в себя такие случаи.
Следует отметить, что в коде практически нет структурных ограничений switch
заявление или о том, где case *:
этикетки размещены в этом коде *. Это делает возможными трюки программирования, такие как устройство Даффа, одна из возможных реализаций которого выглядит следующим образом:
int n = ...;
int iterations = n/8;
switch(n%8) {
while(iterations--) {
sum += *ptr++;
case 7: sum += *ptr++;
case 6: sum += *ptr++;
case 5: sum += *ptr++;
case 4: sum += *ptr++;
case 3: sum += *ptr++;
case 2: sum += *ptr++;
case 1: sum += *ptr++;
case 0: ;
}
}
Видите ли, код между switch(n%8) {
и case 7:
этикетка определенно достижима...
* К счастью, суперкатер указал в комментарии: начиная с C99, ни goto
ни ярлык (будь то case *:
метка или нет) может появиться в рамках декларации, которая содержит декларацию VLA. Поэтому неправильно говорить, что нет никаких структурных ограничений на размещение case *:
этикетки. Однако устройство Даффа предшествует стандарту C99, и оно все равно не зависит от VLA. Тем не менее, я чувствовал себя обязанным вставить "виртуально" в мое первое предложение из-за этого.
Вы получили ответ, связанный с необходимыми gcc
вариант -Wswitch-unreachable
чтобы сгенерировать предупреждение, этот ответ предназначен для уточнения юзабилити / достоинства.
Цитировать прямо из C11
, глава §6.8.4.2, (выделение мое)
switch (expr) { int i = 4; f(i); case 0: i = 17; /* falls through into default code */ default: printf("%d\n", i); }
объект, чей идентификатор
i
существует с автоматическим хранением (в пределах блока), но никогда не инициализируется, и, таким образом, если управляющее выражение имеет ненулевое значение, вызовprintf
Функция получит доступ к неопределенному значению. Точно так же вызов функцииf
не может быть достигнуто
Что очень очевидно. Вы можете использовать это для определения локальной переменной, которая доступна только в пределах switch
Объем заявления.
С его помощью можно реализовать "цикл с половиной", хотя это может быть не лучшим способом сделать это:
char password[100];
switch(0) do
{
printf("Invalid password, try again.\n");
default:
read_password(password, sizeof(password));
} while (!is_valid_password(password));