2014-09-29 2 views
15

Я нашел C код, который имеет такую ​​структуру:do..while внутри переключатель

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 или, если это плохой дизайн. Я не писал этого, и я полагаю, что автор сделал это так, потому что он запустил цикл как можно быстрее.

+13

Похож на вариант [Устройство Даффа] (https://en.wikipedia.org/wiki/Duff's_device). Здесь есть объяснение (https://stackoverflow.com/questions/514118/how-does-duffs-device-work). – tangrs

+2

Подумайте о выражениях 'case' как меток с' switch' как 'goto'. – chux

+1

Это своего рода техника разворачивания петли. подумайте о конце цикла. – Alex

ответ

6

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

Акино Duff's device, чтобы ввести петлю в промежуточное положение. @ tangrs

Общее, нахмурившееся в наши дни как 1), компиляторы обычно лучше справляются с оптимизацией & 2) как обнаружил ОП, может легко затушевать смысл кода. Используйте с осторожностью.

В коде 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; 
    } 
    } 

} 
+0

Спасибо, вам и другим, кто ответил. Теперь это имеет смысл. На самом деле я немного огорчен, что не мог понять это сам. Может быть, я слишком стар для этого! – kh99

+0

@ kh99, сомневаюсь, если слишком старый. Этот метод работает пятно и может удовлетворить потребность в очень разных ситуациях. Но IMO, обслуживание дороже, что оригинальное кодирование и кодирование должны избегать трудного для ведения недокументированных фрагментов, подобных этому. Не такая уж большая потеря, чтобы не прийти к ней раньше. – chux

+0

Я не уверен в части «безусловно чище» второго блока. Но я уверен в его неэквивалентности. В первом блоке 'n' используется только для определения исходного места в цикле и не перепроверяется снова внутри цикла. – John

6

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

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 для всего: для if/else ветвей кода, для циклов, для коммутаторов, для выхода из циклов/переключателей, чтобы избежать рекурсии, и т. д. И если вы злоупотребляете подобным образом, и если вы делаете что-то вроде прыжка на ярлык, который сразу же перескакивает на другой ярлык, люди теряют обзор. Код, подобный этому, нечитабелен и чрезвычайно трудно отлаживать. Вот как вы пишете код сборки, но это не должно быть способом написания кода C.

1

Все случаи - это просто этикетки. Так что, если убрать оператор коммутатора код будет выглядеть

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 { 
     // 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: 

Итак, что же добавить переключатель в код, показанный выше? Он предоставляет только точки входа в петли, минуя обычные точки входа петель. Это первые итерации начнутся с какого-либо ярлыка случая и не более того. Также, если нет соответствующей метки случая, петли будут пропущены.

0

Я не уверен, если мой вариант лучше, но я бы написать

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 с, и на самом деле многие из них и/или сложены сложные условия. Поэтому вместо этого я заменил путаницу 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; 
    } 
} 

Это немного повторяющиеся, но довольно ясно, и использует не падают, хотя.

+0

@chux Спасибо, исправлено. Но теперь это еще более повторяемо. – maaartinus

+0

Правда это - но, по крайней мере, понятно. – chux

0

Я видел такую ​​структуру раньше. Возможно, автор писал какую-то государственную машину. (См. Источник в zlib для примера.)

Выполняет ли часть «делать некоторые вещи» n в любом случае, когда будет работать следующий?

+0

Оригинальный код, который я смотрел, находится здесь: [link] (http://wwwhomes.uni-bielefeld.de/achim/prime_sieve.html). Как вы можете видеть, это реализация решета Эратосфена. Я не верю, что n изменяется в цикле. – kh99