Переключатель: по умолчанию должен быть последний случай? - PullRequest
157 голосов
/ 24 июня 2010

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

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

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

РЕДАКТИРОВАТЬ:
как Джон Кейдж и KillianDS пишите: это действительно уродливый и запутанный код, и я хорошо это знаю.Меня просто интересует общий синтаксис (он определен?) И ожидаемый результат.

Ответы [ 10 ]

76 голосов
/ 24 июня 2010

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

72 голосов
/ 24 июня 2010

Стандарт C99 не содержит явных указаний по этому поводу, но, принимая во внимание все факты, он совершенно действителен.

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

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

Редактировать : код внутри переключателя не является чем-то особенным;это нормальный блок кода, как в if, с дополнительными метками перехода.Это объясняет поведение при падении и необходимость 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); 
} 

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

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

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

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

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

44 голосов
/ 24 июня 2010

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

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

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).

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

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

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

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

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

15 голосов
/ 24 июня 2010

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

8 голосов
/ 24 июня 2010

Нет определенного порядка в операторе 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);
  }
}
7 голосов
/ 24 июня 2010

Один сценарий, в котором я бы посчитал целесообразным расположить «по умолчанию» где-то, кроме конца оператора 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;
}

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

</p>


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.

6 голосов
/ 09 августа 2012

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

  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
  }
5 голосов
/ 23 января 2014

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

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

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

case ModeStretched:
    // write to a file "stretched"
    break;
}
2 голосов
/ 24 июня 2010

Условием «по умолчанию» может быть любое место внутри коммутатора, в котором может существовать предложение 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
1 голос
/ 24 июня 2010

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

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

[редактировать] @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]; 
}
...