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 в любом случае, когда следующий пробег будет запущен?