Рассмотрите следующее switch
оператор:
switch( value )
{
case 1:
return 1;
default:
value++;
// fall-through
case 2:
return value * 2;
}
Этот код компилирует, но действительно ли это допустимо (= определенное поведение) для C90/C99? Я никогда не видел код, где случай по умолчанию не является последним случаем.
Править:
Как Jon Cage и запись KillianDS: это - действительно ужасный и запутывающий код, и я хорошо знаю о нем. Я просто интересуюсь общим синтаксисом (он определяется?) и ожидаемый вывод.
В стандарте 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 соответствует и нет ярлыка по умолчанию, нет части корпус переключателя выполнен.
В операторе 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);
}
}
да, это верно, и при некоторых обстоятельствах это даже полезно. В целом, если вам это не нужно, не делайте этого.
Операторы 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 / по умолчанию), и ваши дела очень длинные. поэтому вы не хотите повторять весь код случая внизу для значения по умолчанию
Условие "по умолчанию" может быть в любом месте переключателя, где может существовать условие 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
Это верно, но довольно неприятно. Я бы сказал, что вообще плохо допускать провалы, так как это может привести к очень грязному спагетти-коду.
Почти наверняка лучше разбить эти случаи на несколько операторов 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];
}
Это действительно и очень полезно в некоторых случаях.
Рассмотрим следующий код:
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 об оптимизации кода.
Тогда это станет неким арбитражем между удобочитаемостью кода и его производительностью. Наверное, лучше поставить комментарий, чтобы объяснить будущему читателю, почему дело появляется первым.
Один из сценариев, в котором я считаю целесообразным иметь «значение по умолчанию», расположенное где-то, кроме конца оператора 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.