Do..while внутри переключателя
Я нашел код C, который имеет эту структуру:
switch (n) {
do {
case 1:
// do some things
if (some condition)
goto go_on;
case 2:
// do some things
if (some condition)
goto go_on;
case 3:
// do some things
if (some condition)
goto go_on;
} while (1);
do {
case 4:
// do some things
if (some condition)
goto go_on;
case 5:
// do some things
if (some condition)
goto go_on;
} while (1);
}
go_on:
Я программировал в C годами (много лет назад) и думал бы, что это будет синтаксическая ошибка. Я думаю, что это связано с оптимизацией цикла, но мне было интересно, может ли кто-нибудь объяснить, что он делает. Что происходит, когда достигается while(1)
, эффективно ли он возвращается к коммутатору? И, в частности, почему есть два дела... в то время как там?
Кстати, я не хочу начинать обсуждение использования goto
, или если это плохой дизайн. Я не писал это, и я полагаю, что автор сделал это так, потому что он запустил цикл как можно быстрее.
Ответы
Ответ 1
Эквивалентный код, который может лучше показывать поток программы.
Подключитесь к устройству Duff, чтобы ввести петлю в промежуточную позицию. @tangrs
Общее нахмурилось в наши дни как 1) компиляторы обычно лучше оптимизируют работу и 2) как обнаружил OP, могут легко скрывать смысл кода. Следует использовать с осторожностью.
В OP-коде после условия while
поток программы не возвращается в оператор switch
. switch
и case
влияют только на начальную запись в циклы while
.
if (n == '1') goto case1;
if (n == '2') goto case2;
...
if (n == '5') goto case5;
goto go_on;
do {
case1:
// do some things
if (some condition) goto go_on;
case2:
// do some things
if (some condition) goto go_on;
case3:
// do some things
if (some condition) goto go_on;
} while (1);
do {
case4:
// do some things
if (some condition) goto go_on;
case5:
// do some things
if (some condition) goto go_on;
} while (1);
go_on:
[изменить]
Есть 2 while
петли, чтобы разместить исходный поток кодера, чтобы перепрыгнуть в промежуточные точки на 1 из 2 циклов.
Далее следует повторная запись кандидата. Имея доступ к общему коду, возможно, более чистое решение.
int n2 = n; // Only evaluate n once as in the switch statement.
if (n2 >= 1) {
if (n2 <= 3) {
while (1) {
if (n2 <= 1) {
// do some things
if (some condition) { break; }
}
if (n2 <= 2) {
// do some things
if (some condition) { break; }
}
// do some things
if (some condition) { break; }
n2 = 1;
}
else if (n2 <= 5) {
while (1) {
if (n2 <= 4) {
// do some things
if (some condition) { break; }
}
// do some things
if (some condition) { break; }
n2 = 4;
}
}
}
Ответ 2
Позвольте мне переписать этот код для вас, возможно, это сделает его более очевидным. Следующий код более или менее эквивалентен тому, который вы опубликовали:
if (n == 1) goto ONE;
if (n == 2) goto TWO;
if (n == 3) goto THREE;
if (n == 4) goto FOUR;
if (n == 5) goto FIVE;
goto SKIP_ALL;
while (true) {
ONE:
// do some things
if (some condition) goto go_on;
TWO:
// do some things
if (some condition) goto go_on;
THREE:
// do some things
if (some condition) goto go_on;
}
while (true) {
FOUR:
// do some things
if (some condition) goto go_on;
FIVE:
// do some things
if (some condition) goto go_on;
}
SKIP_ALL:
go_on:
Петли находятся внутри коммутатора, они не приводят к тому, что коммутатор происходит чаще. Коммутатор в основном решает, в какой цикл скачкообразно сканируется программа, и при которой начинается инструкция в этом цикле. Как только он прыгнул туда, петли продолжаются нормально. Также обратите внимание, что коммутатор обычно быстрее, чем все эти операторы if
.
И нет, goto - неплохой дизайн в целом. A switch
- просто просто goto, а переключатель - неплохой дизайн. В действительности, почти каждая ветвь кода внутри функции/метода, выполняемая на процессоре или внутри виртуальной машины, представляет собой простой переход (иногда условный, иногда нет). Это просто, что goto является самым примитивным, низкоуровневым способом ветвления и мало информирует читателя о намерении. Всякий раз, когда есть более высокий уровень, тот, который делает ваше намерение более очевидным, предпочтительнее использовать его. Использование goto - это всего лишь плохая конструкция, если бы вы могли легко написать такой же код без использования goto, и это было бы не намного хуже. В некоторых (хотя и очень редких) случаях goto почти неизбежен или любая попытка избежать этого создает в результате уродливый, нечитаемый, очень сложный код или очень плохую производительность.
"goto считается вредным" эссе происходит от времени, когда некоторые люди использовали goto для всего: для if/else ветвей кода, для циклов, для коммутаторов, для выхода из циклов/переключателей, чтобы избежать рекурсии и т.д. И если вы злоупотребляете goto, как это, и если вы делаете что-то вроде прыжка на ярлык, который сразу же перескакивает на другой ярлык, люди теряют обзор. Код, подобный этому, нечитабелен и чрезвычайно трудно отлаживать. Вот как вы пишете код сборки, но это не должно быть способ написания кода C.
Ответ 3
Все случаи - это просто метки. Поэтому, если удалить оператор switch, код будет выглядеть как
do {
case '1':
// do some things
if (some condition)
goto go_on;
case '2':
// do some things
if (some condition)
goto go_on;
case '3':
// do some things
if (some condition)
goto go_on;
} while (1);
do {
case '4':
// do some things
if (some condition)
goto go_on;
case '5':
// do some things
if (some condition)
goto go_on;
} while (1);
go_on:
Таким образом, существуют две обычные бесконечные циклы do-while, которые останавливают итерации в зависимости от некоторых внутренних условий в циклах. Вы можете написать этот код еще проще
do {
// do some things
if (some condition)
goto go_on;
// do some things
if (some condition)
goto go_on;
// do some things
if (some condition)
goto go_on;
} while (1);
do {
// do some things
if (some condition)
goto go_on;
// do some things
if (some condition)
goto go_on;
} while (1);
go_on:
Итак, что добавляет переключатель в код, показанный выше? Он предоставляет только точки входа в петли, минуя обычные точки входа петель. Это первые итерации начнутся с какого-либо ярлыка случая и не более того. Также, если нет соответствующей метки случая, петли будут пропущены.
Ответ 4
Я не уверен, что мой вариант лучше, но я бы написал
switch (n) {
LOOP1:
// do some things
if (some condition)
break;
case 2:
// do some things
if (some condition)
break;
case 3:
// do some things
if (some condition)
break;
goto LOOP1;
case 4:
LOOP4:
// do some things
if (some condition)
break;
case 5:
// do some things
if (some condition)
break;
goto LOOP4;
}
Оригинал довольно запутан из-за switch-do-while
. Все решения для замены используют некоторые goto
s, а на самом деле многие из них и/или вложенные сложные условия. Поэтому вместо этого я заменил путаницу do-while
на goto
.
Вы можете упростить
if (some condition)
break;
goto LOOP4;
в
if (!some condition) goto LOOP4;
но я предпочитаю сохранять его равномерным.
Если вы можете извлечь
// do some things
if (some condition)
break;
в функцию, вы можете написать
switch (n) {
case 1: while (true) {
if (body1()) break;
if (body2()) break;
if (body3()) break;
}
break;
case 2: while (true) {
if (body2()) break;
if (body3()) break;
if (body1()) break;
}
break;
case 3: while (true) {
if (body3()) break;
if (body1()) break;
if (body2()) break;
}
break;
case 4: while (true) {
if (body4()) break;
if (body5()) break;
}
break;
case 5: while (true) {
if (body5()) break;
if (body4()) break;
}
}
Он бит повторяющийся, но довольно ясный и не использует fall-хотя.
Ответ 5
Я видел такую структуру раньше. Возможно, автор писал какую-то государственную машину. (См. Источник для zlib для примера.)
Изменена ли часть "сделать некоторые вещи" n в любом случае, когда следующий пробег будет запущен?