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

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

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

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

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

168
задан Gaffi 20 August 2012 в 21:28
поделиться

8 ответов

В стандарте C99 это не уточняется, но, взяв все факты вместе, это совершенно верно.

Метка case и по умолчанию эквивалентна метке goto . См. 6.8.1. Помеченные заявления. Особенно интересен 6.8.1.4, который включает уже упомянутое устройство Даффа:

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

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

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 не может быть достигнуто.

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

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

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

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

78
ответ дан 23 November 2019 в 20:56
поделиться

В операторе switch нет определенного порядка. Вы можете рассматривать случаи как что-то вроде именованной метки, например, метки goto . Вопреки тому, что здесь думают люди, в случае значения 2 метка по умолчанию не перескакивает. Чтобы проиллюстрировать классический пример, вот устройство Даффа , которое является дочерним элементом крайностей 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);
  }
}
8
ответ дан 23 November 2019 в 20:56
поделиться

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

15
ответ дан 23 November 2019 в 20:56
поделиться

Операторы 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 / по умолчанию), и ваши дела очень длинные. поэтому вы не хотите повторять весь код случая внизу для значения по умолчанию

83
ответ дан 23 November 2019 в 20:56
поделиться

Условие "по умолчанию" может быть в любом месте переключателя, где может существовать условие case. Оно не обязательно должно быть последним. Я видел код, в котором условие по умолчанию ставилось в качестве первого пункта. Условие "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
2
ответ дан 23 November 2019 в 20:56
поделиться

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

Почти наверняка лучше разбить эти случаи на несколько операторов 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]; 
}
1
ответ дан 23 November 2019 в 20:56
поделиться

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

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

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 будет обычным значением, которое содержит рекурсивный вызов и выделенные элементы (отдельные случаи) терминальные значения. Обычно нет необходимости сосредотачиваться на конечных ценностях.

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

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

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

47
ответ дан 23 November 2019 в 20:56
поделиться

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

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

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



switch(widget_state)
{
  case WIDGET_IDLE:
    widget_ready = 0;
    widget_hardware_off();
    break;
  case WIDGET_START:
    ...
    break;
  case WIDGET_WHATEVER:
    ...
    break;
  default:
    widget_state = WIDGET_INVALID_STATE;
    /* Fall through */
  case WIDGET_INVALID_STATE:
    widget_ready = 0;
    widget_hardware_off();
    ... do whatever else is necessary to establish a "safe" condition
}

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

7
ответ дан 23 November 2019 в 20:56
поделиться
Другие вопросы по тегам:

Похожие вопросы: