Оператор switch: по умолчанию должен быть последний случай?

Рассмотрим следующий оператор switch:

switch( value )
{
  case 1:
    return 1;
  default:
    value++;
    // fall-through
  case 2:
    return value * 2;
}

Этот код компилируется, но действительно ли он (= определенное поведение) для C90/C99? Я никогда не видел кода, где случай по умолчанию - это не последний случай.

EDIT:
Как пишет Jon Cage и KillianDS: это действительно уродливый и запутанный код, и я хорошо знаю об этом. Меня просто интересует общий синтаксис (определяется ли он?) И ожидаемый результат.

Ответ 1

Стандарт C99 не является явным об этом, но, принимая все факты вместе, он совершенно прав.

A case и default ярлыки эквивалентны метке goto. См. 6.8.1 Обозначенные утверждения. Особенно интересен 6.8.1.4, который позволяет уже упомянутому устройству Duff:

Любому утверждению может предшествовать префикс, который объявляет идентификатор как имя метки. Ярлыки сами по себе не изменяют поток контроля, который продолжает беспрепятственно проходить через них.

Изменить: код внутри коммутатора ничего особенного; это обычный блок кода, как в if -statement, с дополнительными метками перехода. Это объясняет ошибочное поведение и почему break необходимо.

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); 
} 

В фрагменте искусственной программы объект, идентификатор которого существует я с автоматической продолжительностью хранения (внутри блока), но никогда инициализируется, и, следовательно, если управляющее выражение имеет ненулевой значение, вызов функции printf получит доступ к неопределенному значению. Аналогично, вызов функции f не может быть достигнуто.

Константы case должны быть уникальными в инструкции switch:

6.8.4.2.3 Выражение каждой метки случая должно быть целочисленной константой выражение и два случая постоянные выражения в одном и том же оператор switch должен иметь одинаковый значение после преобразования. Может быть не более одной метки по умолчанию в коммутаторе утверждение.

Все случаи оцениваются, затем он переходит к метке по умолчанию, если задано:

6.8.4.2.5 Целевые рекламные акции выполняются на контрольном выражение. Постоянное выражение в каждая метка случая преобразуется в продвинутый тип контролирующего выражение. Если преобразованное значение соответствует рейтингу продвинутого контролирующее выражение, контрольные прыжки к заявлению после согласованного кейс. В противном случае, если есть метка по умолчанию, управление переходит на с надписью. Если нет случайное выражение нет метки по умолчанию, ни одна из частей корпус переключателя выполнен.

Ответ 2

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

Хороший пример: -

switch(5) {
  case 1:
    echo "1";
    break;
  case 2:
  default:
    echo "2, default";
    break;
  case 3;
    echo "3";
    break;
}


Outputs '2,default'

очень полезно, если вы хотите, чтобы ваши дела представлялись в логическом порядке в коде (как, например, в случае 1, случай 3, случай 2/по умолчанию), а ваши случаи очень длинные, поэтому вы не хотите повторять весь код корпуса внизу по умолчанию

Ответ 3

Это действительно и очень полезно в некоторых случаях.

Рассмотрим следующий код:

switch(poll(fds, 1, 1000000)){
   default:
    // here goes the normal case : some events occured
   break;
   case 0:
    // here goes the timeout case
   break;
   case -1:
     // some error occurred, you have to check errno
}

Дело в том, что приведенный выше код более читабельен и эффективен, чем каскадный if. Вы можете поместить значение по умолчанию в конце, но это бессмысленно, так как оно будет фокусировать ваше внимание на случаях ошибок вместо обычных случаев (что здесь есть случай default).

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

В поле программного обеспечения я думаю о другом очень обычном случае: рекурсиях с некоторыми терминальными значениями. Если вы можете выразить это с помощью коммутатора, default будет обычным значением, содержащим рекурсивный вызов и выделенные элементы (отдельные случаи) значениями терминалов. Обычно нет необходимости фокусироваться на значениях терминалов.

Другая причина заключается в том, что порядок дел может изменять скомпилированный код поведения, и это важно для исполнения. Большинство компиляторов будут генерировать скомпилированный ассемблерный код в том же порядке, что и код в коммутаторе. Это делает первый случай очень отличным от других: во всех случаях, кроме первого, будет задействован переход, который будет опустошать процессорные конвейеры. Вы можете понять это как предиктор ветвления, по умолчанию выполняющий первый возникающий случай в коммутаторе. Если дело, если гораздо более распространено то, что у других тогда у вас есть очень веские причины, чтобы выразить это как первый случай.

Чтение представляет собой конкретную причину, по которой исходный плакат задал этот вопрос после прочтения реорганизации Branch Loop для компилятора Intel о оптимизации кода.

Затем будет некоторый арбитраж между считываемостью кода и производительностью кода. Вероятно, лучше поставить комментарий, чтобы объяснить будущему читателю, почему первый случай появляется.

Ответ 4

да, это действительно, и при некоторых обстоятельствах это даже полезно. Как правило, если вам это не нужно, не делайте этого.

Ответ 5

В инструкции switch нет определенного порядка. Вы можете рассматривать случаи как нечто похожее на именованный ярлык, например метку goto. Вопреки тому, что люди, кажется, здесь думают, в случае значения 2 метка по умолчанию не подпрыгивает. Чтобы проиллюстрировать классический пример, здесь устройство Duff, которое является родителем плаката крайностей switch/case в C.

send(to, from, count)
register short *to, *from;
register count;
{
  register n=(count+7)/8;
  switch(count%8){
    case 0: do{ *to = *from++;
    case 7:     *to = *from++;
    case 6:     *to = *from++;
    case 5:     *to = *from++;
    case 4:     *to = *from++;
    case 3:     *to = *from++;
    case 2:     *to = *from++;
    case 1:     *to = *from++;
            }while(--n>0);
  }
}

Ответ 6

Один из сценариев, где я считаю целесообразным иметь "значение по умолчанию", расположенное где-то иначе, чем конец оператора case, находится на конечной машине, где недопустимое состояние должно reset машины и действовать так, как если бы оно было начальным государство. Например:

switch(widget_state)
{
  default:  /* Fell off the rails--reset and continue */
    widget_state = WIDGET_START;
    /* Fall through */
  case WIDGET_START:
    ...
    break;
  case WIDGET_WHATEVER:
    ...
    break;
}

альтернативное расположение, если недопустимое состояние не должно reset машины, но должно быть легко идентифицировано как недопустимое состояние:


Переключатель (widget_state)
{ case WIDGET_IDLE:   widget_ready = 0;   widget_hardware_off();   break;case WIDGET_START:   ...   break;case WIDGET_WHATEVER:   ...   break;по умолчанию:   widget_state = WIDGET_INVALID_STATE;   /* Проваливаться */ case WIDGET_INVALID_STATE:   widget_ready = 0;   widget_hardware_off();   ... делать все, что необходимо для установления "безопасного" состояния
}

Код в другом месте может затем проверить (widget_state == WIDGET_INVALID_STATE) и обеспечить, чтобы какая-либо ошибка, сообщающая об ошибках, или поведение state- reset представляется подходящим. Например, код состояния может отображать значок ошибки, а опция меню "start widget", которая отключена в большинстве состояний без простоя, может быть включена для WIDGET_INVALID_STATE, а также WIDGET_IDLE.

Ответ 7

Включение в другой пример: это может быть полезно, если "по умолчанию" является неожиданным случаем, и вы хотите зарегистрировать ошибку, но также сделать что-то разумное. Пример из моего собственного кода:

  switch (style)
  {
  default:
    MSPUB_DEBUG_MSG(("Couldn't match dash style, using solid line.\n"));
  case SOLID:
    return Dash(0, RECT_DOT);
  case DASH_SYS:
  {
    Dash ret(shapeLineWidth, dotStyle);
    ret.m_dots.push_back(Dot(1, 3 * shapeLineWidth));
    return ret;
  }
  // more cases follow
  }

Ответ 8

Бывают случаи, когда вы конвертируете ENUM в строку или преобразуете строку в перечисление в случае, когда вы пишете/читаете в/из файла.

Иногда вам нужно сделать одно из значений по умолчанию, чтобы покрыть ошибки, сделанные с помощью ручного редактирования файлов.

switch(textureMode)
{
case ModeTiled:
default:
    // write to a file "tiled"
    break;

case ModeStretched:
    // write to a file "stretched"
    break;
}

Ответ 9

Условием "по умолчанию" может быть любое место в коммутаторе, в котором может существовать предложение case. Это не обязательство быть последним пунктом. Я видел код, который помещает значение по умолчанию в качестве первого предложения. "Случай 2:" запускается нормально, даже если предложение по умолчанию находится над ним.

В качестве теста я поместил образец кода в функцию, называемую test (int value) {} и выполнил:

  printf("0=%d\n", test(0));
  printf("1=%d\n", test(1));
  printf("2=%d\n", test(2));
  printf("3=%d\n", test(3));
  printf("4=%d\n", test(4));

Вывод:

0=2
1=1
2=4
3=8
4=10

Ответ 10

Это действительно, но скорее противно. Я бы предположил, что это вообще плохо, чтобы разрешить провалы, так как это может привести к очень грязному коду спагетти.

Почти наверняка лучше разбить эти случаи на несколько операторов switch или меньших функций.

[edit] @Tristopia: Ваш пример:

Example from UCS-2 to UTF-8 conversion 

r is the destination array, 
wc is the input wchar_t  

switch(utf8_length) 
{ 
    /* Note: code falls through cases! */ 
    case 3: r[2] = 0x80 | (wc & 0x3f); wc >>= 6; wc |= 0x800; 
    case 2: r[1] = 0x80 | (wc & 0x3f); wc >>= 6; wc |= 0x0c0; 
    case 1: r[0] = wc;
}

было бы более ясным относительно его намерения (я думаю), если бы оно было написано так:

if( utf8_length >= 1 )
{
    r[0] = wc;

    if( utf8_length >= 2 )
    {
        r[1] = 0x80 | (wc & 0x3f); wc >>= 6; wc |= 0x0c0; 

        if( utf8_length == 3 )
        {
            r[2] = 0x80 | (wc & 0x3f); wc >>= 6; wc |= 0x800; 
        }
    }
}   

[edit2] @Tristopia: Ваш второй пример, вероятно, самый чистый пример хорошего использования для последующего прохождения:

for(i=0; s[i]; i++)
{
    switch(s[i])
    {
    case '"': 
    case '\'': 
    case '\\': 
        d[dlen++] = '\\'; 
        /* fall through */ 
    default: 
        d[dlen++] = s[i]; 
    } 
}

.. но лично я бы разделил распознавание комментария в его собственную функцию:

bool isComment(char charInQuestion)
{   
    bool charIsComment = false;
    switch(charInQuestion)
    {
    case '"': 
    case '\'': 
    case '\\': 
        charIsComment = true; 
    default: 
        charIsComment = false; 
    } 
    return charIsComment;
}

for(i=0; s[i]; i++)
{
    if( isComment(s[i]) )
    {
        d[dlen++] = '\\'; 
    }
    d[dlen++] = s[i]; 
}