Запретный Плод Goto Сладок (Версия Для Микроконтроллера)!

Добрый день!

Запретный плод GOTO сладок (версия для микроконтроллера)!

Как вы относитесь к оператору goto в языках C/C++? Скорее всего, когда вы учились программировать, вы этим пользовались.

Потом ты узнал, что это плохо, и забыл об этом.

Хотя иногда при сложной обработке ошибок.

нет-нет, тут try. throw. catch. Или выйти из вложенных циклов.

нет, там флаги и куча сложностей.

Или когда вложенные переключатели.

нет-нет-нет, там одни и те же флаги.

И все же иногда в ночной тишине ты допускал в свое подсознание греховную мысль – «а почему бы не использовать Прямо здесь идти к? И программа вроде постройнеет, и будет работать оптимально.

Да, было бы неплохо.

Но нет - запрещено , забыл!".

Почему это так? Ниже — небольшое расследование и мое отношение к этому вопросу, основанное на многолетней практике и разных платформах.

Эта статья является аналогом то же самое для С++ , но здесь пункты выделены именно для Си и для микроконтроллеров.

Просьба к ярым противникам гото - не бросайте меня в огненный ад минус кармы только потому, что я поднял эту тему и являюсь частичным сторонником гото!



Краткий исторический экскурс

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



А началось все с комбинационных схем


Запретный плод GOTO сладок (версия для микроконтроллера)!

Вначале было слово — и это слово было функцией.

Не так важно, что это была булева функция логической переменной — тогда в этом базисе удалось реализовать всю (почти) математику, а потом и тексты, и графику.

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

Другими словами, вам нужно было сделать устройство, которое по значению переменной (переменных) находило значение функции.

Для решения этой сложнейшей проблемы был последовательный алгоритм выполнения арифметических действий (в случае заданной точности вычислений в таком алгоритме каждая арифметическая операция может быть выполнена за один такт).

Имея алгоритм, нетрудно построить комбинационную схему — схему, мгновенно (с точностью работы логических устройств и времени распространения сигнала) выдающую ответ на выходе.

Вопрос: нужны ли здесь какие-то переходы? Нет, их здесь просто нет. Существует последовательный поток действий.

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



Но затем вмешались схемы памяти.



Запретный плод GOTO сладок (версия для микроконтроллера)!

И тут чей-то умный разум придумал схему обратной связи — например, RS-триггер.

И тут появилось состояние схема.

А состояние — это не что иное, как текущее значение всех элементов с памятью.

Появление таких элементов памяти позволило совершить революционный скачок от жестко запрограммированных устройств к микропрограммным автоматам.

Проще говоря, микропрограммированные машины имеют память команд. Есть отдельное устройство, реализующее текущую прошивку (сложение, вычитание или что-то еще).

А вот выбором «текущей» прошивки занимается отдельное устройство — пусть это будет «устройство выборки».

Вопрос – есть ли здесь переходы? Определенно да! При этом появляются безусловные переходы (адрес следующей команды не зависит от текущего состояния данных) и условные (адрес следующей команды зависит от состояния данных).

Можно ли обойтись без них? Ни за что! Если не использовать переходы, то вернемся к комбинационной схеме без памяти.



В итоге мы пришли к ассемблеру
Апофеозом таких вычислительных устройств стали микро-, простые- и суперкомпьютеры.

Все они основаны на кодовом языке, который довольно легко конвертируется в язык Ассемблера с примерно идентичным набором команд. Рассмотрим какой-нибудь средний ассемблер микроконтроллера (я знаком с ассемблером для ATmeg, PIC и AT90).

Как происходит его переход? Переходы могут быть безусловными (просто переход к следующему адресу, переход к подпрограмме, выход из нее) и условными (в зависимости от состояния флагов).

Заявляю со всей ответственностью - Без операций перехода в ассемблере обойтись невозможно! Любая программа на ассемблере ими просто пестрит! Впрочем, думаю, здесь со мной никто не будет спорить.



Нижняя граница
Какой вывод можно сделать? На уровне микропроцессора очень активно используются операции ветвей.

Написать настоящую программу, не использующую их, практически невозможно (может быть, это и можно сделать, но это было бы супер-мега-извращение и уж точно не настоящая программа!).

С этим тоже никто не спорит. Но почему же тогда в языках более высокого уровня — давайте сосредоточимся на C для микроконтроллеров — оператор goto вдруг потерял популярность?.



Немного об алгоритмах



Запретный плод GOTO сладок (версия для микроконтроллера)!

Теперь давайте посмотрим на хитрый алгоритм.

Понятия не имею, что это за ерунда – но это надо реализовать.

Предположим, что это техническая спецификация.

Здесь A, B, C, D, E — некоторые операции , а не вызов функции! Вполне возможно, что они используют много локальных переменных.

И вполне возможно, что они изменят свое состояние.

То есть в данном случае речь не идет о вызове функций — не будем подробно останавливаться на некоторых действиях.

Вот как это выглядит с помощью goto:

  
  
  
  
  
  
   

if (a) { A; goto L3; } L1: if (b) { L2: B; L3: C; goto L1; } else if (!c) { D; goto L2; } E;

Очень лаконично и читабельно.

Но - запрещено! Попробуем без goto:

char bf1, bf2, bf3; if (a) { A; bf1 = 1; } else bf1 = 0; bf2 = 0; do { do { if (bf3 || b) bf3 = 1; else bf3 = 0; if (bf3 || bf2) B; if (bf3 || bf1 || bf2) { C; bf1 = 0; bf2 = 1; } if (!bf3) { if (!c) { D; bf3 = 1; } else { bf3 = 0; bf2 = 0; } } } while (bf3); } while (bf2); E;

Вы что-нибудь поняли из логики второго листинга?.

Давайте сравним оба листинга:

  • На первый листинг я потратил в 5 раз меньше времени, чем на второй.

  • Листинг с goto короче как минимум в 2 раза.

  • Листинг с goto сможет понять любой человек с минимальной подготовкой в C. Второй я постарался сделать максимально доступным и очевидным — и все равно вникать в него нужно долго.

  • Сколько времени займет отладка первого варианта и сколько времени займет отладка второго?
  • И вообще, если рассматривать нарисованный алгоритм как постановку задачи, то первый листинг верен на 100%.

    Насчет второго я пока не очень уверен.

    по крайней мере в порядке проверки условий и флагов.

  • Сравните полученный ассемблерный код первого и второго листингов.

Но во втором листинге нет перехода! Больше для меня предложенный Реализуйте этот алгоритм примерно так:

if a A C while b or not c if not b D B C E

Кажется, красиво, правда? Почему бы не сделать копипаст, почему бы не добавить дополнительные проверки, почему бы и нет. делай что угодно, кроме перехода!!! Ну да ладно, такие алгоритмы в жизни почти не встречаются.

Лучше говорить о жизни.



перейти в реальные программы

За свой более чем 20-летний опыт работы я работал на нескольких аппаратных платформах и десятке языков программирования, участвовал в написании крупного программного продукта.

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

Короче говоря, я не силен в программировании.

Ах да, забыл - я закончил с почетным дипломом ХНУР? - То есть, по идее, я и тебя выпорю.

Поэтому мои последующие мысли и ситуации.

скажем так, я имею на них моральное право.



Неявное использование goto
В языке C есть много операторов, которые на самом деле являются простым переходом — условным или безусловным.

Это все типы циклов for (.

), while (.

) {.

}, do {.

} while (.

).

Это анализ числовых переменных switch (.

) {case.case.}.

Это одни и те же операторы прерывания/перехода в циклах прерывания и продолжения.

В конце концов, это вызовы функций funct() и их возвраты.

Эти goto считаются «легальными» — почему сам goto незаконен?

В чем обвиняют Гото?
Его обвиняют в том, что он делает код нечитабельным, плохо оптимизированным, могут появляться ошибки.

Речь идет о практических недостатках.

А теоретические просто плохие и безграмотные, вот и все! Что касается нечитабельности кода и плохой оптимизации, взгляните еще раз на листинги выше.

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

Но это все! Являются ли другие инструменты C безопасными и не могут ли они создавать ошибки в коде? Да, возьмите хотя бы преобразования типов, работу с указателями.

Нет смысла там возиться! Вам не кажется, что нож – очень опасная вещь? Но почему-то мы используем его на кухне.

А 220 вольт это ужасно опасно! Но если использовать его с умом, то можно жить.

То же самое касается и перехода.

Нужно использовать его с умом — и тогда код будет работать корректно.

Что касается теоретических рассуждений, то это, простите меня, спор о вкусах.

Вы используете Венгерская нотация ? Нет, я терпеть ее не могу! Но я не говорю, что она из-за этого плохая! Лично я считаю, что переменная должна нести смысловую нагрузку — то, для чего она создана.

Но я не буду запрещать другим людям использовать этот метод именования! Или есть эстеты, которые считают, что писать а=++i неграмотно, надо писать i=i+1; а = я.

И что теперь, запретить и это?

Обработка ошибок
Возьмем обработку входных пакетов от какого-нибудь внешнего устройства:

pack = receive_byte (); switch (pack) { case ‘A’: for (f = 0; f < 10; ++f) { … if (timeout) goto Leave; … } break; case ‘B’: … } Leave: …

Мы получили заголовок пакета.

Проанализировано.

Да, пакет «А» означает, что нам нужно сделать что-то 10 раз.

Не забываем контролировать время работы этого раздела – а вдруг другая сторона зависнет? Ага, зависло — сработало условие таймаута — дальше выходим наружу — из цикла, из коммутатора.

То же самое можно сделать и с использованием всяких флагов - но это будет работать медленнее, плюс займёт столь дефицитную ячейку памяти.

Это стоит того? Это простой случай.

Но get_byte() также может быть макро-функцией с обработкой тайм-аута.

И такие резкие выходы тоже будут. Это как раз тот случай, когда я активно использую goto. Это позволило мне не застревать при проблемах с внешними устройствами, UART, USB и т.д.

Выход из вложенного цикла наружу
Взгляните на программу ниже:

char a, b, c; for (a = 0; a < 10; ++a) { for (b = 0; b < a; ++b) { if (!c) goto Leave; } for (b = 10; b < 15; ++b) { d (); } } Leave: e ();

Понятно, что происходит? Есть вложенный цикл.

При возникновении какого-либо условия мы прекращаем всю последующую обработку.

Этот код с флагами выглядит иначе:

char a, b, c, f1; f1 = 1; for (a = 0; a < 10 && f1; ++a) { for (b = 0; b < a && f1; ++b) { if (!c) f1 = 0; } if (f1) { for (b = 10; b < 15; ++b) { d (); } } } e ();

Что произошло в этом случае? Теперь на каждой итерации мы проверяем флаг.

Не забудьте проверить это дальше.

Это мелочи, если итераций мало и речь идет о «безразмерной» памяти на ПК.

А когда программа пишется для микроконтроллера, это всё становится значимым.

Кстати, в связи с этим в некоторых языках (если не ошибаюсь, в Java) есть возможность выхода из цикла с помощью метки Break Leave. Кстати, то же самое! Могу привести точно такой же пример с обработкой в switch(.

){case.}.

Я часто с этим сталкиваюсь при обработке входящих пакетов неодинаковой структуры.



Автоматическая генерация кода
Знакомы ли вы с автоматическим программированием? Или любая другая автоматизированная генерация кода? Скажем, создатели лексических обработчиков (без использования громоздкого boost::spirit).

Все эти программы создают код, который можно использовать как «черный ящик» — вам все равно, что внутри; Тебе небезразлично, что он делает. А внутри goto используется очень и очень часто.



Выход в одном месте
В C иногда приходится писать что-то вроде:

int f (…) { … if (a) { c = 15; return 10; } … if (b) { c = 15; return 10; } … с = 10; return 5; }

Этот код будет выглядеть намного аккуратнее следующим образом:

int f (…) { … if (a) goto Exit; … if (b) goto Exit; … с = 10; return 5; Exit: c = 15; return 10; }

Идея ясна? Иногда нужно что-то сделать, уходя.

Иногда Много вещей должно быть сделано.

И тогда goto здесь очень помогает. У меня тоже есть такие примеры.

Вроде всё перечислил, теперь могу подвести итоги.



Нижняя граница

Этот мой точка зрения! И это правда для меня.

Может быть, для вас, но я не буду вас заставлять следовать этому! Итак, для меня очевидно, что goto помогает решить некоторые задачи более оптимально и эффективно.

Но бывает и наоборот — goto может вызвать массу проблем.

УПД : Прочитав гору комментариев, я выделил для себя положительные и отрицательные стороны использования goto. Плюсы использования goto :

  • наиболее оптимальный (с точки зрения листинга и результирующего кода) вывод из нескольких вложенных циклов и переключения.

    случая

  • C: наиболее экономичный (с точки зрения листинга и результирующего кода) метод обработки ошибок.

  • в индивидуальных случаях наиболее оптимальная конструкция алгоритма
  • экономит память и тактовые циклы, когда аккуратный использование, что иногда имеет первостепенное значение
Недостатки использования goto :
  • незнание кода
  • нарушение потока чтения листинга сверху вниз и стандартизированного обхода блоков в коде ( в том смысле, что можно как переместиться в центр блока, так и выйти из него )
  • усложняя процесс оптимизации кода для компилятора (а иногда и делая его невозможным)
  • увеличение вероятности создания незначительных ошибок в вашем коде
Кто еще может сказать мне плюсы/минусы? Я напишу, если они обоснованы.

Еще раз обращу ваше внимание: Я не сторонник везде использовать goto ! НО в некоторых случаях это позволяет реализовать алгоритм гораздо эффективнее, чем все остальные средства.

Теги: #goto #C++ #микроконтроллер #Ассемблер #Алгоритмы #Программирование микроконтроллера

Вместе с данным постом часто просматривают: