Допустимый, но бесполезный синтаксис в 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));
Другие вопросы по тегам