2017-01-18 8 views
204

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

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

Возможно, вы не прочитали вопрос: ** ДА ** компилятор должен предупредить о недостижимом коде. ** НЕТ ** этот бит был * NOT * помечен как недостижимый. – abelenky

+3

@ KerrekSB попытался воспроизвести: у моего GCC 5.3.1 нет. –

ответ

220

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

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

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


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

Так замена int i = 4; с int i; делает его действительным C++.

+2

"... но никогда не инициализируется ..." Похоже, что 'i' инициализируется до 4, что мне не хватает? – yano

+7

Обратите внимание, что если переменная является 'static', она будет инициализирована равной нулю, поэтому для этого также существует безопасное использование. – Leushenko

+22

@yano Мы всегда переходим через инициализацию 'i = 4;', поэтому она никогда не происходит. – AlexD

15

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

Опция -Унимый код was removed in gcc 4.4 дальше.

+0

Испытывая проблему, из первых рук всегда помогает! – 16tons

+0

@JonathanLeffler: Общая проблема gcc-предупреждений, восприимчивых к определенному набору оптимизирующих проходов, по-прежнему остается верной и, к сожалению, приводит к плохой работе с пользователями. Очень неприятно иметь чистую сборку Debug, за которой следует неудачная версия Release:/ –

+0

@MatthieuM: Казалось бы, такое предупреждение было бы чрезвычайно легко обнаружить в случаях, связанных с грамматическим, а не семантическим анализом (например, код следует за «if», который имеет возвраты в обеих ветвях], и облегчает подавление «раздражающих» предупреждений. С другой стороны, есть некоторые случаи, когда полезно иметь ошибки или предупреждения в сборках релизов, которые не находятся в отладочных сборках (если не что иное, в тех местах, где предполагается, что взломан, который помещается для отладки, будет очищен для выпуск). – supercat

10

Вы получили ваш ответ, связанные с required gcc option-Wswitch-unreachable генерировать предупреждение, этот ответ является разработка на практичности/worthyness часть.

Цитирование прямо из 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.

98

Действительно ли это служит какой-либо цели?

Да. Если вместо заявления, вы положили заявление перед первым лейблом, это может сделать прекрасный смысл:

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() доступен.

+7

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

+8

@ MarcusMüller: * Устройство Даффа *, страшно да ... –

+0

@MatthieuM У устройства Duff есть метки меток внутри цикла, но начинается с метки корпуса перед циклом. – hvd

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:'? – i486

+0

@ i486 Когда 'i = 4', он не запускает' nocase'. –

+1

@ njzk2 Я человек, который любит его gotos. Сделка с этим B- \ –

11

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

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 thankfully pointed out in a comment: Поскольку C99, ни один из goto, ни метка (будь то метка case *:), могут отображаться в пределах декларации, содержащей декларацию VLA. Поэтому неправильно говорить, что существуют no структурные ограничения на размещение меток case *:. Однако устройство Duff предшествует стандарту C99, и в любом случае это не зависит от VLA. Тем не менее, я был вынужден вставить «фактически» в мое первое предложение из-за этого.

+0

Добавление массивов переменной длины привело к наложению структурных ограничений, связанных с ними. – supercat

+0

@supercat Какие существуют ограничения? – cmaster

+1

Ни один из ярлыков 'goto' и' switch/case/default' не может появляться в рамках любого объекта или типа с переменной переменной. Это эффективно означает, что если блок содержит любые объявления объектов или типов массива переменной длины, перед этими объявлениями должны предшествовать любые метки. В стандарте есть путаница в формулировке словесности, которая предполагает, что в некоторых случаях область применения VLA-декларации распространяется на весь оператор switch; см. http://stackoverflow.com/questions/41752072/meaning-of-this-rule-about-variable-length-arrays-and-switch-statements для моего вопроса. – supercat

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 Это каламбур или что? Пожалуйста, объясни. – Dancia

+1

@ Dancia Он говорит, что это довольно ясно - не лучший способ сделать что-то подобное, а «не может» - это что-то вроде преуменьшения. –

37

Существует известная польза этого называется Duff's 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 = * с ++; '. –

+1

@BenVoigt Я бы сказал, что положить 'do {' более читабельным. Да, спорить о читаемости для устройства Даффа - глупо и бессмысленно и, вероятно, простой способ сойти с ума. –

+1

@QPaysTaxes Вы должны проверить [Коутины в C] Симона Татхама (http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html). А может и нет. –