Допустимый, но бесполезный синтаксис в switch-case?

213

Через небольшую опечатку я случайно нашел эту конструкцию:

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 действителен, но также полностью недоступен.

Я получил чистую компиляцию, даже не предупредив о недостижимом коде, но это кажется бессмысленным.

Если компилятор помечен как недостижимый код?
Это вообще служит какой-либо цели?

  • 0
    Разве ваш компилятор уже не предупреждает вас о мертвом коде вообще?
  • 1
    Возможно, вы не читали вопрос: ДА, компилятор должен предостеречь от недоступного кода. НЕТ, этот бит НЕ был помечен как недоступный.
Показать ещё 16 комментариев
Теги:
switch-statement
language-lawyer

8 ответов

231
Лучший ответ

Возможно, не самый полезный, но не совсем бесполезный. Вы можете использовать его для объявления локальной переменной, доступной в области 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 невозможен.

P.S. Кстати, образец недействителен С++-кодом. В этом случае (N4140 6.7/3, акцент мой):

Программа, которая перескакивает 90 из точки, где переменная с автоматической продолжительностью хранения не имеет точка, где она находится в области видимости, плохо сформирована , если переменная не имеет скалярного типа, тип класса с тривиальным значением по умолчанию конструктор и тривиальный деструктор, cv-квалифицированная версия одного из этих типов или массив одного из предшествующие типы и объявляется без инициализатора (8.5).


90) Передача из условия оператора switch на метку case считается прыжком в этом отношении.

Таким образом, замена int i = 4; на int i; делает его допустимым С++.

  • 3
    «... но никогда не инициализируется ...» Похоже, i инициализирован до 4, что мне не хватает?
  • 7
    Обратите внимание, что если переменная является static , она будет инициализирована нулем, поэтому для этого также есть безопасное использование.
Показать ещё 4 комментария
93

Это вообще служит цели?

Да. Если вместо утверждения вы помещаете декларацию перед первым ярлыком, это может иметь смысл:

switch (a) {
  int i;
case 0:
  i = f(); g(); h(i);
  break;
case 1:
  i = g(); f(); h(i);
  break;
}

Правила деклараций и операторов разделяются для блоков в целом, поэтому это же правило позволяет это также разрешать там инструкции.


Также стоит упомянуть, что если первый оператор представляет собой конструкцию цикла, в теле цикла могут появляться метки меток:

switch (i) {
  for (;;) {
    f();
  case 1:
    g();
  case 2:
    if (h()) break;
  }
}

Пожалуйста, не пишите такой код, если есть более читаемый способ его записи, но он совершенно корректен, а вызов f() доступен.

  • 8
    стрелять, да, я видел эту конструкцию петли раньше, но совершенно забыл об этом. Scaaary.
  • 8
    @ MarcusMüller: Устройство Даффа , страшно, да ...
Показать ещё 3 комментария
37

Существует известное использование этого устройства Duff Device.

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.

  • 2
    Конечно, это все еще было бы возможно без разрешения операторов выше первого случая, так как порядок do { и case 0: не имеет значения, оба служат для размещения цели перехода на первом *to = *from++; ,
  • 1
    @BenVoigt Я бы поспорил, что размещение do { более читабельно. Да, спорить о читабельности устройства Даффа глупо и бессмысленно, и, вероятно, это простой способ сойти с ума.
Показать ещё 1 комментарий
15

Предполагая, что вы используете gcc в Linux, это дало бы вам предупреждение, если вы используете 4.4 или более раннюю версию.

Опция -Wunreachable-code была удалена в gcc 4.4 дальше.

  • 0
    Испытав проблему, из первых рук всегда помогает!
  • 0
    @JonathanLeffler: Общая проблема, связанная с тем, что предупреждения gcc чувствительны к конкретному набору выбранных проходов оптимизации, к сожалению, все еще остается верной и затрудняет работу пользователей. Очень неприятно иметь чистую сборку Debug с последующей неудачной сборкой Release: /
Показать ещё 4 комментария
11

Следует отметить, что практически никаких структурных ограничений для кода внутри оператора switch или на том, где метки case *: помещаются внутри этого кода *. Это делает программные трюки, такие как устройство duff, одна возможная реализация которого выглядит следующим образом:

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: определенно доступен...


* Как supercat благодарно отметил в комментарии: С C99, ни a goto, ни метка (будь то метка case *: или не) может отображаться в рамках декларации, содержащей декларацию VLA. Поэтому не следует утверждать, что структурных ограничений на размещение меток case *: не существует. Однако устройство Duff предшествует стандарту C99, и в любом случае оно не зависит от VLA. Тем не менее, я был вынужден вставить "фактически" в мое первое предложение из-за этого.

  • 0
    Добавление массивов переменной длины привело к наложению структурных ограничений, связанных с ними.
  • 0
    @supercat Какие ограничения?
Показать ещё 3 комментария
11

Не только для объявления переменных, но и для переходов. Вы можете использовать его хорошо, если и только если вы не склонны к коду спагетти.

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 */

Следует отметить, что коммутационный футляр является одним из самых быстрых предложений потока управления. Поэтому он должен быть очень гибким для программиста, который иногда включает такие случаи.

  • 0
    И в чем разница между nocase: и default: nocase:
  • 0
    @ i486 Когда i=4 он не запускает nocase .
Показать ещё 4 комментария
10

Вы получили свой ответ, связанный с обязательным параметром 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.

9

С ним можно реализовать "цикл с половиной", хотя это может быть не самый лучший способ сделать это:

char password[100];
switch(0) do
{
  printf("Invalid password, try again.\n");
default:
  read_password(password, sizeof(password));
} while (!is_valid_password(password));
  • 5
    "может не"? ;-)
  • 0
    @RichardII Это игра слов или что? Пожалуйста, объясни.
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню