Действительный, но бесполезный синтаксис в коммутаторе?
Через небольшую опечатку я случайно нашел эту конструкцию:
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
действителен, но также полностью недоступен.
Я получил чистую компиляцию, даже не предупредив о недостижимом коде, но это кажется бессмысленным.
Если компилятор помечен как недостижимый код?
Это вообще служит какой-либо цели?
Ответы
Ответ 1
Возможно, не самый полезный, но не совсем бесполезный. Вы можете использовать его для объявления локальной переменной, доступной в области 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
существует с автоматической продолжительностью хранения (внутри блока), но никогда инициализируется, и, следовательно, если контрольное выражение имеет ненулевое значение, вызов функции printf
будет доступ к неопределенному значению. Аналогично, вызов функции f
невозможен.
P.S. Кстати, образец недействителен С++-кодом. В этом случае (N4140 6.7/3
, акцент мой):
Программа, которая перескакивает 90 из точки, где переменная с автоматической продолжительностью хранения не имеет точка, где она находится в области видимости, плохо сформирована , если переменная не имеет скалярного типа, тип класса с тривиальным значением по умолчанию конструктор и тривиальный деструктор, cv-квалифицированная версия одного из этих типов или массив одного из предшествующие типы и объявляется без инициализатора (8.5).
90) Передача из условия оператора switch
на метку case считается прыжком в этом отношении.
Таким образом, замена int i = 4;
на int i;
делает его допустимым С++.
Ответ 2
Это вообще служит цели?
Да. Если вместо утверждения вы помещаете декларацию перед первым ярлыком, это может иметь смысл:
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()
доступен.
Ответ 3
Существует известное использование этого устройства Duff 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
.
Ответ 4
Предполагая, что вы используете gcc в Linux, это дало бы вам предупреждение, если вы используете 4.4 или более раннюю версию.
Опция -Wunreachable-code была удалена в gcc 4.4 дальше.
Ответ 5
Не только для объявления переменных, но и для переходов. Вы можете использовать его хорошо, если и только если вы не склонны к коду спагетти.
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 */
Следует отметить, что коммутационный футляр является одним из самых быстрых предложений потока управления. Поэтому он должен быть очень гибким для программиста, который иногда включает такие случаи.
Ответ 6
Следует отметить, что практически никаких структурных ограничений для кода внутри оператора switch
или на том, где метки case *:
помещаются внутри этого кода *. Это делает программные трюки, такие как устройство duff, одна возможная реализация которого выглядит следующим образом:
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 благодарно отметил в комментарии: С C99, ни a goto
, ни метка (будь то метка case *:
или не) может отображаться в рамках декларации, содержащей декларацию VLA. Поэтому не следует утверждать, что структурных ограничений на размещение меток case *:
не существует. Однако устройство Duff предшествует стандарту C99, и в любом случае оно не зависит от VLA. Тем не менее, я был вынужден вставить "фактически" в мое первое предложение из-за этого.
Ответ 7
Вы получили свой ответ, связанный с обязательным параметром gcc
-Wswitch-unreachable
, чтобы сгенерировать предупреждение, этот ответ должен уточнить в части удобства использования/достоинства.
Цитата прямо из 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
.
Ответ 8
С ним можно реализовать "цикл с половиной", хотя это может быть не самый лучший способ сделать это:
char password[100];
switch(0) do
{
printf("Invalid password, try again.\n");
default:
read_password(password, sizeof(password));
} while (!is_valid_password(password));