Форум программистов, компьютерный форум, киберфорум
Наши страницы

Как работает оператор switch в Си/Си++

Войти
Регистрация
Восстановить пароль
Рейтинг: 3.67. Голосов: 6.

Как работает оператор switch в Си/Си++

Запись от Evg размещена 15.02.2012 в 20:36
Обновил(-а) Evg 10.06.2015 в 22:10
Метки c++

ВНИМАНИЕ! Вопросы по существу обсуждаемого вопроса просьба задавать здесь или создать тему на форуме и кинуть на неё ссылку в блог или мне в личку.
Объясняю почему

Причин для этого несколько.

Я, как и любой другой автор, всегда могу упустить интересный момент обсуждаемой темы (что подтвердилось на практике). А потому задаваемый вопрос может закрывать пробел в статье. Ответ на конкретный вопрос, как правило, дать несложно. Сложнее его аккуратно сформулировать так, чтобы ответ являлся законченной частью статьи. Поэтому, как правило, на первых порах я ограничиваюсь конкретным ответом на конкретный вопрос, а в статью временно вставляю ссылку на пост, где был дан ответ. А когда дойдут руки, то вместо ссылки пишу нормальное пояснение. Технические возможности блога не позволяют в комментариях пользоваться широкими возможностями, доступными на форуме (то как выделение текста жирным, вставка фрагментов исходников в удобном для чтения виде и т.п.), поэтому будет удобнее, если вопрос и ответ будут опубликованы на форуме

Любая статья является изложением знаний в общем случае. У многих людей мышление устроено так, что прочтя на форуме конкретный вопрос и конкретный ответ на этот вопрос, у них появится бОльшее понимание, чем после прочтения теоретических выкладок (даже если они подкреплены конкретными примерами). Ссылки на такие обсуждения я, как правило, включаю в последний раздел статьи.

Начинающие, как правило, поиск ответов на свои вопросы ведут именно в форуме, а не в блогах. А потому конкретный вопрос и конкретный ответ для них будет более удобным и полезным именно на форуме. Многие люди умеют работать методом тыка, лишь бы был конкретный пример в качестве образца. А потому такое обсуждение будет им полезным даже без прочтения статьи

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





1. Как обычно поясняется работа switch'а в учебниках

Так или иначе все программисты считают, что они знают ответ на этот вопрос. Однако это не так. В большинстве случаев знания почерпаны из книг и учебников или являются эмпирическими результатами самостоятельных исследований. Но, как показала практика, большинство программистов (к их числу когда-то относился и я) на самом деле не знают точной семантики (смысла) оператора switch.

Начну с простого примера

C
switch (x)
{
  case 10:
    y = 1;
    break;
  case 11:
  case 12:
    y = 2;
    break;
  default:
    y = 3;
    break;
}
в учебниках обычно поясняется, что данный пример является эквивалентом примера

C
if (x == 10)
  {
    y = 1;
  }
else if (x == 11 || x == 12)
  {
    y = 2;
  }
else
  {
    y = 3;
  }
С точки зрения исполнения программы эти примеры действительно эквивалентны. Более того, большинство компиляторов скорее всего построят для этих примеров один и тот же код (или очень близкий). Однако такое объяснение НЕ верно, но об этом чуть ниже

Те, кто перешёл с Паскаля на Си, очень часто спотыкаются о необходимость расставлять break'и. В паскале конструкция case (а точнее, её альтернатива, ибо я не помню, как правильно в паскале писать) означает автоматическое завершение кода предыдущей альтернативы и уход на конец switch'а. В учебниках сей факт как правило поясняют тем, что break можно не ставить, и в этом случае произойдёт провал (fall) на следующую альтернативу. Ну и как частный случай - два подряд идущих case'а являются вырожденным случаем провала. Необходимость построения настоящих (невырожденных) провалов на практике является статистически редким явлением, и потому многих необходимость написания break'а сильно раздражает.

2. Как на самом деле работает switch

Оператор switch на самом деле является коммутируем переходом. Т.е. в зависимости от значения ключа (то, что стоит в скобках после слова switch) происходит переход на ту или иную метку, а операторы case определяют эти самые метки. Продемонстрирую это на примере, содержащем в том числе провалы (отсутствие break'ов).

C
switch (x)
{                 <--- скобка 1
  case 10:
    y = 1;
    break;
  case 11:
  case 12:
    y = 2;
    /* break; */  <--- тут уберём break и устроим провал
  default:
    y = 3;
    break;
}                 <--- скобка 2
Семантическим эквивалентом данному коду будет

C
/* Данный набор операторов if и goto есть семантический эквивалент
 * оператора switch (x) */
if (x == 10)
  goto L10;
else if (x == 11)
  goto L11;
else if (x == 12)
  goto L12;
else
  goto Ldefault;
{                        <--- скобка 1
L10:                     <--- метка "case 10"
  y = 1;
  goto Finish;           <--- break
L11:                     <--- метка "case 11"
L12:                     <--- метка "case 12"
  y = 2;
  /* goto Finish; */     <--- закомментированный break
Ldefault:                <--- метка "default"
  y = 3;
  goto Finish;           <--- break
}                        <--- скобка 2
Finish:                  <--- метка за пределами switch'а, куда ведут все break'и
                              эта метка полностью эквивалентна тому, что есть
                              в циклах for и while
Вот так на самом деле работает оператор switch. Если посмотреть на пример пояснения из учебника (раздел 1) и данный пример, то с виду кажется, что принципиальных отличий одного от другого нет и второе элементарно сводится к первому. Конкретно в данном случае это действительно так. Но в данном примере я пояснил лишь принцип действия оператора switch, но не заострял внимания на его синтаксисе, о чём будет сделано в следующем разделе

3. То, о чём редко пишут в учебниках

Поискав через google описание работы switch'а, я много раз натыкался на описание синтаксиса. Этот синтаксис сводится к следующему (здесь привожу вариант с MSDN):

Код:
switch (expression)  {
  case constant_expression_1 : statement_1;
  ...
  case constant_expression_n : statement_n;
[default: statement_n+1]
}
Такое описание является НЕ верным, потому что синтаксис оператора switch в языках Си/Си++ определяется НЕ так. Но чтобы проще было понять, вернёмся к тому, с чем все хорошо знакомы - к оператору if. Синтаксис оператора if в самом простейшей случае (без else) неформальным образом можно выразить так:

Код:
if (expression)
  body
Где "body" - это либо одиночный statement, либо группа statement'ов, заключённых в фигурные скобки. Если мы рассмотрим синтаксис оператора while, то выглядит он аналогичным образом:

Код:
while (expression)
  body
но при этом отличие от варианта if'а заключается в том, что "body" может содержать в себе операторы break и continue (которые на самом деле являются операторами goto на неявные метки). Синтаксис switch'а определяется абсолютно таким же образом:

Код:
switch (expression)
  body
при этом "body" может содержать внутри себя метки case и переходы break

Теперь рассмотрим синтетический (т.е. не из реальной жизни) пример, который на первый взгляд многим может показаться диким, потому что идёт в разрез с имеющимся представлением о работе switch'а:

C
switch (x)
  if (z == 5)
    {
    case 10:
      y = 1;
    }
  else
    {
    case 11:
      if (z > 10)
        y = 2;
      else
        {
      default:
          y = 3;
        }
    }
С точки зрения языка Си данный пример является абсолютно корректным. Классическое пояснение из учебников (см. раздел 1) не в состоянии объяснить работу этого примера. Пример описания синтаксиса, данный в начале данного раздела, вообще будет считать этот пример некорректным с точки зрения языка (как минимум потому, что после switch'а отсутствует открывающая фигурная скобка). И только правильное пояснение (см. раздел 2) в состоянии описать, как же будет работать этот пример.

C
if (x == 10)        <--- начало оператора switch
  goto L10;
else if (x == 11)
  goto L11;
else
  goto Ldefault;    <--- конец оператора switch
  if (z == 5)       <--- начало "body"
    {
    L10:            <--- метка "case 10"
      y = 1;
    }
  else
    {
    L11:            <--- метка "case 11"
      if (z > 10)
        y = 2;
      else
        {
      Ldefault:     <--- метка "Ldefault"
          y = 3;
        }
    }
При таком раскладе можно понять, что операторы if, написанный в самом начале switch'а, является мёртвым кодом (т.е. туда управление никогда не передастся). Однако перед ним мы можем поставить метку (обычную или case) и тогда мёртвых кодов не останется. Если "body" switch'а реализовано через фигурные скобки (как это делается в обычной жизни), то эти фигурные скобки стандартным образом задают лексический блок, в котором можно описывать переменные (критично для Си):

C
switch (x)
{
  int a, b;   <--- переменные a и b доступны только внутри фигурных скобок
 
  case 10:
    a = x + 1;
    b = x - 1;
    y = a * b;
    break;
  ...
}
Напоследок хочется сказать, что на практике такими "дебильными" конструкциями пользуются очень и очень редко. На практике я пока только однажды встречал конструкцию, являющуюся вариацией алгоритма под названием Duff's device

C
void send (int *to, const int *from, int count)
{
  int n = (count + 7) / 8;
  switch (count % 8)
  {
    case 0: do { *to++ = *from++;
    case 7:      *to++ = *from++;
    case 6:      *to++ = *from++;
    case 5:      *to++ = *from++;
    case 4:      *to++ = *from++;
    case 3:      *to++ = *from++;
    case 2:      *to++ = *from++;
    case 1:      *to++ = *from++;
            } while (--n > 0);
  }
}
Данный пример обсуждался в теме Что делает данный код и зачем такое кому-нибудь может понадобиться?. Предполагаемый ответ на вопрос был озвучен в 10-м посте указанной темы.

4. Для чего придумали оператор switch

С виду кажется, что switch в "нормальном" случае принципиально ничем не отличается от цепочки конструкций if - else if - ... По принципу действия действительно не отличается. Однако с точки зрения генерации кода switch принципиально строится по другому. Проще всего это опять пояснить на коротком примере.

C
switch (x)
{
  case 5:
    y = 10;
  case 6:
    y = 25;
  case 7:
    y = 38;
  ...
  case 20:
    y = 125;
  default:
    y = 1000;
}
В данном примере я не стал ставить break'и, чтобы они не путались под ногами. В нижеидущем примере-аналоге используется конструкция "&&" - операция взятия адреса на метку. В языках Си и Си++ такой операции нет, однако с точки зрения генерируемого кода ничто не мешает тому, чтобы такая операция была, потому как в любом процессоре есть соответствующая аппаратная операция. Именно так рассуждали разработчики компилятора gcc и в своё расширение языка эту операцию внесли (ptr = &&L). А так же операцию перехода по указателю на метку (goto *ptr). Поэтому в примере-аналоге я буду использовать именно эту операцию. Таким образом наш switch аналогичен примеру:

C
/* Массив меток перехода на 16 альтернатив switch'а (от 5 до 20) */
static void *arr[16] = { &&L5, &&L6, &&L7, ..., &&L20 };
 
/* При таких значениях x мы попадаем в один из case'ов switch'а,
 * адрес для перехода загрузим из таблицы arr. При этом надо понимать, что
 * нулевой элемент таблицы соответствует значению x = 5. */
if (x >= 5 && x <= 20)
  goto *arr[x-5];
else
  goto L_default;
 
L5:
  y = 10;
L6:
  y = 25;
L7:
  y = 38;
  ...
L20:
  y = 125;
L_default:
  y = 1000;
Мы видим, что вместо длинной цепочки из 16-и if'ов мы имеем очень коротенький код по динамическому переходу (переходу на заранее неизвестный адрес). Важно понимать, что скорость исполнения этого кода НЕ зависит от размеров таблицы: т.е. switch из 1000 элементов работает с такой же скоростью, как switch из 5 элементов (чего не скажешь о цепочке if'ов). В то время как массив с метками переходов является статически инициализированным (т.е. в бинарном файле лежит уже заполненная таблица, которую в run-time не надо перевычислять).

Есть некоторые тонкие моменты, остающихся на усмотрение компилятора. Например, если в switch'е мы имеем всего две альтернативы 1 и 1000000, то таблица, построенная по такому принципу, занимала бы миллион слов (4 мегабайта в 32-битном режиме). Компилятор в таком случае вместо динамического перехода построит цепочку из двух if'ов. Есть ещё куча аналогичных моментов, которые я не стану описывать, потому что для общего понимания предназначения switch'а эти моменты не являются критичными

5. Ссылки на темы, где обсуждался данный вопрос
Всего комментариев 23

Комментарии

  1. Старый комментарий
    Аватар для Nameless One
    Цитата:
    Напоследок хочется сказать, что на практике такими "дебильными" конструкциями пользуются очень и очень редко. На практике я пока только однажды встречал конструкцию, являющуюся вариацией примера из Страуструпа
    Эту штуку придумал не он: http://en.wikipedia.org/wiki/Duff's_device

    Я считаю, что неплохим дополнением к твоей статье будет описание еще одного реального примера использования таких особенностей оператора switch — реализация простейших сопрограмм в ANSI C: оригинал, перевод
    Запись от Nameless One размещена 28.04.2012 в 07:37 Nameless One вне форума
    Обновил(-а) Nameless One 28.04.2012 в 07:50
  2. Старый комментарий
    Аватар для Evg
    Эти алгоритмы надо вкурить для начала. Чисто по внешнему виду я не особо понял, надо сопровождающий текст почитать. Про Страуструпа поправил
    Запись от Evg размещена 28.04.2012 в 20:19 Evg вне форума
  3. Старый комментарий
    Аватар для Pure
    кудлато. прошел по ссылкам выше.
    это кусок оттуда

    C++ (QT)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    
    #define crBegin static int state=0; switch(state) { case 0:
    #define crReturn(i,x) do { state=i; return x; case i:; } while (0)
    #define crFinish }
     
    int function(void) {
        static int i;
        //crBegin;
        static int state=0;
     
        switch(state)
        {
     
        case 0:
     
        for (i = 0; i < 10; i++)
            //crReturn(1, i);
            do {
            state=1;
            return i;
     
         case 1:;
     
            } while (0);
        }
    }
     
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
     
     
        qDebug()<<function()<<function()<<function()<<function()<<function()<<function();
        return a.exec();
    }


    Цитата:
    выход 5 4 3 2 1 0
    в общем первое и самое броское. ПОЧЕМУ эти д***бы любят запутывать людей? на кой туlа втыкать while.
    все работает и БЕЗ него, достаточно скобочек. с while только еще более запутанно

    C++
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    int function(void) {
        static int i;
       static int state=0;
     
        switch(state)
        {
     
        case 0: //если state==0   ; 
     
        for (i = 0; i < 10; i++) //входим в цикл увеличиваем i
           { //просто скобочки. открывают тело for
            state=1; //устанавливаем state = 1
            return i; //возвращаем текущий i
     
            case 1:; //если state ==1 
              //то мы проскакиваем return и попадаем на окончание итерации фора, 
              //далее новая итерация начинает выполнятся
              // идет увеличение счетчика i установка state (хотя онo и так 1) и возврат i
              //(из ругани компилятора я понял что ; нужно для закрытия метки)
            } //закрываeт тело for.
        }
    }
    выход тот же

    Огромное спасибо Nameless One за то что заострил внимание на этих примерах, там далее по ссылкам они расписали интересный механизм. Однако то что вот так втыкается лишний текст в и без того непростые примеры вызывает неприязнь к авторам. уже поздно а на досуге обязательно посмотрю для себя как оно работает.

    Евгений сори за флуд.
    Запись от Pure размещена 28.04.2012 в 23:38 Pure вне форума
    Обновил(-а) Pure 29.04.2012 в 00:27
  4. Старый комментарий
    Аватар для Nameless One
    Цитата:
    Сообщение от Pure Просмотреть комментарий
    в общем первое и самое броское. ПОЧЕМУ эти д***бы любят запутывать людей? на кой туlа втыкать while.
    [...skipped...]
    с while только еще более запутанноОднако то что вот так втыкается лишний текст в и без того непростые примеры вызывает неприязнь к авторам. уже поздно а на досуге обязательно посмотрю для себя как оно работает
    вообще-то, это распространенный (о нем даже в разных Coding-Style гайдах пишут) прием для написания макросов сложнее одной строчки (statement'а), и используют его не зря.

    Можно привести пример. Пусть у нас есть гипотетический макрос, который выводит два выражения (и один вызов printf использовать нельзя):
    C
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    int printf(const char*, ...);
     
    #define TEST(EXPR1, EXPR2)                        \
        printf(#EXPR1 ": %d\n", (EXPR1));             \
        printf(#EXPR2 ": %d\n", (EXPR2))
     
    int main(void)
    {
        TEST(1 + 2, 1 / 2);
        return 0;
    }
    Смотрим, какой код генерируется препроцессором (лишние переводы строки я буду удалять из листинга):
    Bash
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    ~/samples/c/temp $ cpp macro.c
    # 1 "macro.c"
    # 1 "<built-in>"
    # 1 "<command-line>"
    # 1 "macro.c"
    int printf(const char*, ...);
     
    int main(void)
    {
        printf("1 + 2" ": %d\n", (1 + 2)); printf("1 / 2" ": %d\n", (1 / 2));
        return 0;
    }
    ~/samples/c/temp $ 
    Результат выполения программы очевиден, и я его приводить не буду.

    Изменим программу, чтобы макрос использовался в конструкции if:
    C
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    int printf(const char*, ...);
     
    #define TEST(EXPR1, EXPR2)                        \
        printf(#EXPR1 ": %d\n", (EXPR1));             \
        printf(#EXPR2 ": %d\n", (EXPR2))
     
    int main(void)
    {
        if(0)
            TEST(1 + 2, 1 / 2);
     
        return 0;
    }
    Результат
    Bash
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
    ~/samples/c/temp $ ./sample 
    1 / 2: 0
    ~/samples/c/temp $ cpp macro.c
    # 1 "macro.c"
    # 1 "<built-in>"
    # 1 "<command-line>"
    # 1 "macro.c"
    int printf(const char*, ...);
     
    int main(void)
    {
        if(0)
            printf("1 + 2" ": %d\n", (1 + 2)); printf("1 / 2" ": %d\n", (1 / 2));
     
        return 0;
    }
    ~/samples/c/temp $ 
    будет неочевиден для новичка, но нам понятно, что это из-за того, что ветвь if не была обрамлена в фигурные скобки (блок).

    Исправим это (тупо запихнув тело макроса в фигурные скобки).
    Результат
    Bash
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
    ~/samples/c/temp $ ./sample 
    ~/samples/c/temp $ cpp macro.c
    # 1 "macro.c"
    # 1 "<built-in>"
    # 1 "<command-line>"
    # 1 "macro.c"
    int printf(const char*, ...);
     
    int main(void)
    {
        if(0)
            { printf("1 + 2" ": %d\n", (1 + 2)); printf("1 / 2" ": %d\n", (1 / 2)); };
     
        return 0;
    }
    ~/samples/c/temp $ 
    нас вроде бы устраивает. Но что, если нам нужно ввести в тот же if ветвь else?
    C
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
    int printf(const char*, ...);
     
    #define TEST(EXPR1, EXPR2)                        \
        {                                             \
            printf(#EXPR1 ": %d\n", (EXPR1));         \
            printf(#EXPR2 ": %d\n", (EXPR2));         \
        }        
     
    int main(void)
    {
        if(0)
            TEST(1 + 2, 1 / 2);
        else
            printf("Else clause. Have to be executed\n");
        
        return 0;
    }
    Программа не компилируется: получаем "«else» without previous «if»". Смотрим вывод препроцессора:
    Bash
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
    ~/samples/c/temp $ cpp macro.c
    # 1 "macro.c"
    # 1 "<built-in>"
    # 1 "<command-line>"
    # 1 "macro.c"
    int printf(const char*, ...);
     
    int main(void)
    {
        if(0)
            { printf("1 + 2" ": %d\n", (1 + 2)); printf("1 / 2" ": %d\n", (1 / 2)); };
        else
            printf("Else clause. Have to be executed\n");
     
        return 0;
    }
    ~/samples/c/temp $ 
    После блока стоит разделитель инструкций — точка с запятой, поэтому идущая следом ветвь else уже не может относится к if, в соответствии с грамматикой языка. Тут-то и приходит на помощь конструкция do-while():
    C
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    int printf(const char*, ...);
     
    #define TEST(EXPR1, EXPR2)                        \
        do                                            \
        {                                             \
            printf(#EXPR1 ": %d\n", (EXPR1));         \
            printf(#EXPR2 ": %d\n", (EXPR2));         \
        } while(0)
     
    int main(void)
    {
        if(0)
            TEST(1 + 2, 1 / 2);
        else
            printf("Else clause. Has to be executed\n");
        
        return 0;
    }
    Результат:
    Bash
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    ~/samples/c/temp $ ./sample 
    Else clause. Has to be executed
    ~/samples/c/temp $ cpp macro.c
    # 1 "macro.c"
    # 1 "<built-in>"
    # 1 "<command-line>"
    # 1 "macro.c"
    int printf(const char*, ...);
    # 10 "macro.c"
    int main(void)
    {
        if(0)
            do { printf("1 + 2" ": %d\n", (1 + 2)); printf("1 / 2" ": %d\n", (1 / 2)); } while(0);
        else
            printf("Else clause. Has to be executed\n");
     
        return 0;
    }
    ~/samples/c/temp $ 
    Так что одних скобочек недостаточно.
    Запись от Nameless One размещена 29.04.2012 в 04:31 Nameless One вне форума
    Обновил(-а) Nameless One 29.04.2012 в 04:46
  5. Старый комментарий
    Аватар для Evg
    Про конструкцию "do { ... } while (0)" я уже как-то пояснял тут в разделе 2.5
    Запись от Evg размещена 29.04.2012 в 11:51 Evg вне форума
  6. Старый комментарий
    Аватар для Catstail
    Непонимание сути SWITCH есть закономерное следствие изжития GOTO (во всех видах) из идеологии программирования. Для тех, кто постарше (и программировал на Фортране-4 с его вычисляемыми GOTO) содержание статьи вполне очевидно... Что, впрочем, нисколько не умаляет пользы статьи.
    Запись от Catstail размещена 29.04.2012 в 15:09 Catstail вне форума
  7. Старый комментарий
    Аватар для Pure
    да уж.....эвон как оно. это хитрый трюкер оказывается. забираю ругательные слова, сказанные по незнанию, в сторону авторов. и все таки не знаю как вам, а мне проще было смотреть полное тело функции, а не красивое короткое, но спрятанное за макросами.
    Запись от Pure размещена 30.04.2012 в 16:33 Pure вне форума
    Обновил(-а) Pure 30.04.2012 в 17:32
  8. Старый комментарий
    Аватар для Nameless One
    Pure, увы, не все можно сделать с помощью функций. Макросы для отладки — это наше все.
    Запись от Nameless One размещена 30.04.2012 в 16:35 Nameless One вне форума
  9. Старый комментарий
    Аватар для Pure
    в общем если подвести резюме то мы с Nameless One говорили о разном но в процессе сей беседы я познал интересный трюк. НО.. возвращаюсь к тому что я хотел сказать по поводу данного конкретного примера
    Код C++ (QT)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    #define crBegin static int state=0; switch(state) { case 0:
    #define crReturn(i,x)  { state=i; return x; case i:;}
    #define crFinish }
     
    int function(void) {
        static int i;
     
        crBegin;
    for (i = 0; i < 10; i++)
            crReturn(1, i);
        crFinish
    }
     
     
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
     
        qDebug()<<function()<<function()<<function()<<function();
        return a.exec();
    }
    выход 3 2 1 0

    итого в ДАННОМ КОНКРЕТНОМ ПРИМЕРЕ не нужен do while(0). в теле макроса хватит и просто скобок.

    ругательные слова в адрес авторов возвращать не буду, так как далее по тексту в ссылке идут примеры с ифами, но в том, который я пытался разобрать - while(0) лишний
    Запись от Pure размещена 30.04.2012 в 23:28 Pure вне форума
    Обновил(-а) Pure 30.04.2012 в 23:51
  10. Старый комментарий
    Аватар для Nameless One
    Цитата:
    итого в ДАННОМ КОНКРЕТНОМ ПРИМЕРЕ не нужен do while(0). в теле макроса хватит и просто скобок.
    Конструкция do-while(0) нужна для всех макросов сложнее одного statetement-а (кроме специфических случаев, как с сrBegin). Может, для данной функции фигурных скобок и хватит, но в if-else макрос не скомпилируется:
    Код C
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    
    #include <stdio.h>
    #include <limits.h>
    #include <stdlib.h>
     
    #define crBegin static int state = 0; switch(state) { case 0:
    #define crFinish }
     
    #ifndef EVIL_MACRO
    #define crReturn(x)                             \
        do                                          \
        {                                           \
            state = __LINE__;                       \
            return (x);                             \
        case __LINE__: ;                            \
        } while(0)
    #else
    #define crReturn(x)                             \
        {                                           \
            state = __LINE__;                       \
            return (x);                             \
        case __LINE__: ;                            \
        }
    #endif   
     
    int func(void)
    {
        static int i;
     
        crBegin;
        for(i = 0; i < 10; ++i)
            if(i & 1)
                crReturn(i);
            else
                crReturn(-i);
        crReturn(INT_MAX);
        crFinish;
    }
     
    int main(void)
    {
        int result;
        
        while((result = func()) != INT_MAX)
            printf("%d\n", result);
        
        exit(0);
    }
    Если определить при компиляции макрос EVIL_MACRO, то препроцессор определит твою версию макроса со скобками, и программа не скомпилируется:
    Код Bash
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
    ~/samples/c $ make main # версия с do-while(0)
    cc   main.o   -o main
    ~/samples/c $ ./main 
    0
    1
    -2
    3
    -4
    5
    -6
    7
    -8
    9
    ~/samples/c $ rm main
    ~/samples/c $ CFLAGS="-DEVIL_MACRO" make main # версия со скобками
    cc -DEVIL_MACRO    main.c   -o main
    main.c: В функции «func»:
    main.c:33:9: ошибка: «else» without a previous «if»
    make: *** [main] Ошибка 1
    ~/samples/c $ 
    Вывод: версия с do-while(0) будет работать везде, где работает версия с фигурными скобками, а также во всех остальных случаях.

    Ну и в заключение цитата из Linux Kernel Coding Style:
    Цитата:
    Macros with multiple statements should be enclosed in a do - while block:

    Код C
    1
    2
    3
    4
    5
    
    #define macrofun(a, b, c)           \
        do {                    \
            if (a == 5)         \
                do_this(b, c);      \
        } while (0)
    Запись от Nameless One размещена 01.05.2012 в 03:47 Nameless One вне форума
  11. Старый комментарий
    Аватар для Pure
    прежде хочу сказать что про то, что как бы надо оборачивать в do while(0) я почерпнул из твоих постов, заметок Евгения и твоих ссылок. Но раз ты не перестаешь повторять это. то прокомментируй ВЫХОД
    обрати внимание на эвил макро, он без всяких там do while(0). да чего уж там придумывать это считай целиком представленная тобой же для теста функция.
    Запись от Pure размещена 01.05.2012 в 18:13 Pure вне форума
  12. Старый комментарий
    Аватар для Evg
    Цитата:
    Сообщение от Catstail Просмотреть комментарий
    Непонимание сути SWITCH есть закономерное следствие изжития GOTO (во всех видах) из идеологии программирования
    На самом деле непонимание оно от незнания. Один раз в учебнике не так объяснили, и такие знания прижились на всю жизнь. Если человеку объяснить, то большинство, как мне кажется, логику поймут сразу

    Цитата:
    Сообщение от Catstail Просмотреть комментарий
    и программировал на Фортране-4 с его вычисляемыми GOTO
    Думаю, что в современных бэйсиках, поддерживающих нумерованные строки, оператор ON ... GOTO никуда не делся
    Запись от Evg размещена 02.05.2012 в 00:40 Evg вне форума
  13. Старый комментарий
    Аватар для Nameless One
    Pure, а ты точки с запятой поставь после crReturn, чтобы твоя функция выглядела как нормальный код на C/C++.

    Пойми, никто тебя не заставляет писать crReturn через do-while(0), это всего-навсего good practice.
    Запись от Nameless One размещена 02.05.2012 в 02:44 Nameless One вне форума
  14. Старый комментарий
    Аватар для Pure
    ты серьезно чтоли?


    Код C++
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
     
     
       if(i&1)
        {
           state = __LINE__;                       
           return (i);                             
           case __LINE__: ;  
        };
        else
         {
            state = __LINE__;                       
            return (-i);                             
            case __LINE__: ;  
             
         };
    т.е. вот так надо писать? вот уж не подумал бы что это называется гуд практис. иметь точку с запятой после скобарей ифа да еще чтоб это все работало из*бываться с do while(0) в макросе.
    Для кого Евгений старался расказывая во что разворачиваются макросы?

    В общем то всей писаниной я хотел лишь напомнить, что авторитеты они хороши, но не забывайте о личной практике. И перед тем как делать перепост, поэксперементируйте немного. А то ведь кто то прочитав громкие слова примет на веру.
    Запись от Pure размещена 02.05.2012 в 22:05 Pure вне форума
  15. Старый комментарий
    Аватар для Evg
    > т.е. вот так надо писать?

    Он имел в виду вот так:

    Код C
    1
    2
    3
    4
    5
    
        for(i = 0; i < 10; ++i)
                if(i&1)
                crReturn(i); /* <--- вот тут поставь ";" */
                else
                crReturn(-i); /* <--- и вот тут ";" */
    И после этого оно работать перестанет. Можно писать как ты без ";", но оно выглядеть будет коряво. Собственно, имелось в виду только это
    Запись от Evg размещена 02.05.2012 в 22:44 Evg вне форума
 
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2018, vBulletin Solutions, Inc.