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

    Доброго времени суток!

    Какое Ваше отношение к оператору goto в языках С/С++? Скорее всего, когда Вы учились программировать, Вы его использовали. Потом Вы узнали, что это плохо, и Вы о нем позабыли. Хотя иногда при сложной обработке ошибок… нет-нет, там try … throw … catch. Или же для выхода из вложенных циклов … не-ет, там флаги и куча сложностей. Или когда вложенные switch … нет-нет-нет, там те же флаги.
    И все-таки, иногда в ночной тиши Вы допускали в свое подсознание грешную мысль – «а почему бы не использовать вот тут goto? И программа вроде как стройней будет, и оптимально выходит. Да-а, было бы хорошо… Но нет – нельзя, забыли!».
    А почему так оно?
    Под катом – небольшое расследование и мое, основанное на многолетней практике и разных платформах, отношение к этому вопросу. Эта статья — аналог такой же для С++, но здесь выделены моменты именно для С и для микроконтроллеров.

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

    Небольшой исторический экскурс


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

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



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

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

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

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

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



    А потом чья-то умная голова додумалась до схемы с обратной связью – например, RS-триггер. И тогда появилось состояние схемы. А состояние – это ни что иное, как текущее значение всех элементов с памятью.

    Появление таких элементов памяти позволило сделать революционный скачок вперед от жестко заданных устройств к микропрограммным автоматам. Упрощенно говоря, в микропрограммных автоматах есть память команд. Есть отдельное устройство, которое реализует текущую микропрограмму (сложение, вычитание или еще чего). А вот выбором «текущей» микропрограммы занимается отдельное устройство – пусть это будет «устройство выборки».

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

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

    В итоге мы пришли к ассемблеру


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

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

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

    Итог


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

    Но почему же тогда в языках более высокого уровня – сконцентрируемся на С для микроконтроллеров — оператор 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 поймет любой человек с самой минимальной подготовкой в С. Второй же я постарался сделать максимально доступным и очевидным – и все равно, в него надо долго вникать.
    • Сколько времени уйдет на отладку первого варианта и сколько на отладку второго?
    • И вообще, если считать нарисованный алгоритм постановкой задачи, то первый листинг правильный на 100%. Про второй я до сих пор не очень уверен… хотя бы в очередности проверки условий и флагов.
    • Сравните получившийся ассемблерный код первого и второго листинга.


    Но зато во втором листинге нет goto!

    Еще мне предлагали этот алгоритм реализовать приблизительно так:


    if a
    A
    C

    while b or not c
    if not b
    D
    B
    C

    E



    Вроде бы красиво, да? Почему бы не сделать copy-past, почему бы не накрутить дополнительные проверки, почему бы… сделать все что угодно, кроме goto!!!

    Ну да ладно, в жизни такие алгоритмы почти не встречаются. Лучше поговорим о жизни.

    goto в реальных программах



    Я за свой более чем 20-летний стаж прошел несколько аппаратных платформ и с десяток языков программирования, участвовал в написании крупного программного продукта ActiveHDL, делал коммерческую базу данных и много небольших программ для отладки оборудования, используемого в Олимпийских играх, а также делал устройства для этой самой Олимпиады (уже несколько Олимпиад, если быть точным). Короче, что-то я в программировании шарю. А, да, забыл – я закончил с почетным дипломом ХНУРЭ — то бишь, в теории я тоже секу.

    Поэтому мои последующие размышления и ситуации… скажем так, я имею моральное право на них.

    Неявное использование goto


    В языке С есть много операторов, которые на самом деле являются банальным goto – условным или безусловным. Это все виды циклов for (…), while (…) {…}, do {…} while (…). Это анализ числовых переменных switch (…) {case … case …}. Это те же операторы прерывания/перехода в циклах break и continue. В конце концов, Это вызовы функций funct() и выход из них return.

    Эти goto считаются «легальными» — чем же нелегален сам goto?

    В чем обвиняют goto


    Обвиняют его в том, что код становится нечитабельным, плохо оптимизируемым и могут появиться ошибки. Это про практические минусы. А теоретические – это просто плохо и неграмотно, и все тут!

    Насчет нечитабельности кода и плохой оптимизируемости – еще раз взгляните на листинги выше.
    Насчет вероятности появления ошибок – согласен, такой код воспринимается несколько сложнее из-за того, что мы привыкли читать листинг сверху вниз. Но и все! А что, другие средства С безопасные и не могут создать ошибок в коде? Да взять хотя бы преобразования типов, работа с указателями. Там напортачить — за нечего делать!

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

    Тоже самое и goto. Пользоваться им надо с умом – и тогда код будет работать корректно.

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

    Или же есть эстеты, которые считают, что писать a = ++i неграмотно, надо писать i = i + 1; a = i. И что теперь, запретить и это тоже?

    Обработка ошибок


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

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


    Мы получили заголовок пакета. Проанализировали. Ага, пакет 'A' — значит, нам надо 10 раз сделать чего-то. Мы не забываем контролировать время работы этого участка — а вдруг вторая сторона зависла? Ага, таки зависла — сработало условие timeout — тогда выходим наружу — из цикла, из switch.

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

    Это случай простой. А ведь receive_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 ();
    


    Что произошло в данном случае? На каждой итерации мы теперь проверяем флаг. Не забываем его проверять и дальше. Это мелочи, если итераций немного и речь идет о «безразмерной» памяти у PC. А когда программа написана для микроконтроллера – это все уже становится существенно.

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

    Точно такой же пример я могу привести и с обработкой в switch (…) { case …}. С этим я сталкиваюсь часто при обработке входящих пакетов неодинаковой структуры.

    Автоматическое создание кода


    Знакомы ли Вы с автоматным программированием? Или любым другим автоматизированным созданием кода? Скажем, создатели лексических обработчиков (без использования громоздкого boost::spirit). Все эти программы создают код, который можно использовать как «черный ящик» — Вам не важно, что там внутри; Вам важно, что он делает. А внутри там goto используется очень и очень часто…

    Выход в одном месте


    На С иногда приходится писать что-то вроде:

    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 может породить массу проблем.

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

    Плюсы использования goto:
    • самый оптимальный (с т. зр. листинга и результирующего кода) выход из нескольких вложенных циклов и switch… case
    • Си: наиболее экономичный (по листингу и результирующему коду) способ обработки ошибок
    • в отдельно взятых случаях самое оптимальное построение алгоритма
    • экономит память и такты при аккуратном использовании, что иной раз бывает первостепенно важным

    Минусы использования goto:
    • непривычность кода
    • нарушение хода следования чтения листинга сверху вниз и стандартизированного обхода блоков в коде (в смысле, что возможен переход в центр блока, а также выход из его)
    • усложнение компилятору (а иногда и невозможность) процесса оптимизации кода
    • повышение вероятности создания трудноуловимых ошибок в коде


    Кто еще подскажет плюсы/минусы? Впишу, если они будут оправданы.

    Еще раз обращаю внимание: я не призываю тулить goto везде! НО в некоторых случаях он позволяет реализовать алгоритм куда эффективней всех остальных средств.
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 69

      +7
      Логика программы без goto оперирует абстракцией «блок». Я вижу — блок ветвления, блок циклов, блок переключателей. Я могу в уме следить, какие из блоков будут выполняться и быстро анализировать программу.

      Логика программы c goto спускает меня на уровень абстракции «инструкция». Я вынужден читать каждую инструкцию и пытаться понять, куда пойдет выполнение программы в следующей строке. Я не могу охватить взглядом весь метод, потому что он связан внутри себя миллионом безусловных переходов, передающих управление в самое неожиданное место.

      В вашем первом примере, в частности, если бы блок-схема была для реального процесса, а переменные назыаны по-человечески (а не одной буквой), то логика была бы гораздо понятнее.
        +1
        А вы не лепите 20 гото, сделайте goto входной точкой перед блоком, и вы опять сможете разделять программу на блоки, а не на инструкции
          0
          при таком подходе, ваши блоки проще объявлять функциями и выходить из них return-ом. И блоки будут более явными, и не придётся каждый блок просматривать на уровне инструкций для того, чтоб понять следует ли он тому соглашению, которое «кажется принято в данном фрагменте кода у других блоков».
            0
            Здесь мой подход к программированию сильно разнится с подходом большинства: я не люблю делить программу на процедуры, которые являются всего-лишь шагами выполнения. Обычно мои прикладные приложения, работающие в режиме даэмона, делятся на три части: инициализация, основная часть, блок обработки ошибок. Инициализируясь, программа переходит в основную часть и циклично бесконечно (в завимости от того что делает программа, главная функция может просто создать пару тредов, а сама просто наблюдать за ними) выполняет одно и тоже. Если случается ошибка, выполнение переходит в третью часть, обрываются все соединения, высвобождается память, и выполняется обратный переход в основую часть.
            Я пишу прикладные программы без малого лет 7 и такая архитектура себя полностью оправдала, хотя мои учителя по программированию конечно были вахЪ от моего подхода.
              0
              Для недлинных программ такое сойдет. Но когда строк кода под тысячу… У меня программы, как правило, разбиты на много файлов и кучу функций. Плюс — reusability (или как там по аглицки :-)? ...)
                0
                Да какая разница кто сколько лет как пишет? Интереснее вопрос, общались с людьми, которые должны ваши программы поддерживать, и не предпочли ли они заново переписать вместо того чтобы с вашим подходом разбираться.
                  0
                  Этого не знаю, т. к. я работаю в основном сольно :-)
                    –1
                    Если бы кому-то пришлось разбираться с моими прикладушками, этот кто-то мог бы не более чем за час разбить главную функцию на несколько шагов и делать с ними что захочет, у меня один блок на другой не лез.
                  0
                  return требует больше — значительно больше! — тактов контроллера, чем простой goto. Во-первых, операция перехода и выхода. Во-вторых, работа со стеком. В-третьих, передача параметров.
                  В противоположность goto — одна операция.
                    0
                    inline-функции спасут
                      0
                      inline-функция — это рекомендация компилятору, в особенности для микроконтроллеров. Visual Studio поддерживает что-то вроде __forceinline, но он тоже гарантированно в inline не превращает
                        0
                        он игнорирует её просто так, или из соображений оптимизации скорости?
                          0
                          Не знаю, честно говоря… Самому непонятно почему
                            0
                            А вот только что в соседней теме увидел: например, в случае рекурсивной функции inline разворачивать без толку. Хотя, я б предпочёл, чтоб мне компилятор в этом случае ошибку выдал, а не просто проигнорировал inline.
                      +1
                      UPD: Именно для этого нужен компилятор — чтоб человеко-читаемую структуру кода развернуть в цепочку команд процессора. Обернуть неограниченные возможности goto в короткий ряд отдельных конкретных и легче понимаемых способов применения.
                        0
                        Вам ведь и в предыдущей теме точно так же на такой же коммент ответили, «инлайн функции» :)
                        Или хотите сказать для микроконтроллеров нет нормальных компиляторов, которые умеют оптимизировать? С gcc я бы не стал соревноваться на эту тему, уж больно крутым надо быть и немало времени потратить на каждый случай чтобы его переоптимизировать.
                          0
                          Опять же — не весь код можно превратить в inline.
                            0
                            С современными C++ компиляторами сложно бороться, чтобы функция не превратилась в инлайн. Я честно не знаю, как с этим дело в си и в микроконтроллерах в частности.
                            0
                            Уделать GCC для AVR (с другими не пробовал) на ассемблере раз плюнуть. Он конечно, хорошо оптимизирует по сравнению с другими Си компилерами для AVR, но нативный асмовский код все равно получается короче.
                            0
                            Это, кстати, одна из причин (но не единственная) из-за которой я пользуюсь goto вместо функций. Я люблю говорить «стэк не резиновый», обычно мне на это отвечают «сейчас на дворе XXI век, а ты пишешь программы, будто до сих пор под DOS'ом сидишь» (хотя я никогда не сидел под Досом, компа ещё не было). На мои слова про кол-во тактов все вообще не обращают внимания, прикрываясь тем же самым «XXI веком на дворе» и «парой наносекунд выигрыша».
                            Люди отвыкли писать тонкие шустрые аскетичные вещи и ленятся добавить пару строк кода, что бы сохранить мегабайт оперативной памяти. Из-за таких мы и имеем программы, жрущие по 500 МБ и это только во время инициализации.
                              0
                              Да, я лично тоже стараюсь писать программы в первую очередь оптимальные. И если для PC с таким подходом можно поспорить — из-за тогое XX1 века,- то для микроконтроллеров это очень и очень важно
                                0
                                Научившись писать неоптимизированные программы для PC, программист не научится писать оптимизированные для какого-нибудь Cannoo, а там его с подходом «ой да ладно, чё там какой-то МБ ОЗУ» ждёт печаль.
                      0
                      Я согласен с тем, что бездумное раскидывание goto ведет к трудности чтения алгоритма.
                      Приведенная блок-схема условна — просто какой-то пример. Поэтому и имена там такие же :-)…
                        0
                        А как насчет примера из учебника:

                        if (a > 0) {goto inner};
                        ... // команды
                        {
                        X ax = X(a);
                        ... // команды
                        inner:
                        // Сюда произойдёт переход по goto
                        ...
                        // По завершении всех команд блока компилятор вызовет
                        // деструктор ~X() для переменной ax.
                        }

                        А конструктор для этой переменной не вызывался ведь?
                        Мне всё равно кажется, что использование GOTO без сильной необходимости разрушает принципы структурного программирования, но в одном вы правы точно — нельзя просто вычёркивать этот оператор из языка.
                          –3
                          Можно и нужно. Goto не дает ничего, чего нельзя было бы достичь другими средствами, зато ухудшает восприятие.
                            +2
                            Мы говорим о микроконтроллерах — так ведь? И о С
                              +6
                              А если мы говорим о контроллерах, то там GOTO в порядке вещей, ибо код получается компактнее и быстрее, и в реалиях микроконтроллеров это гораздо важнее православности сионизма!
                                0
                                православности сионизма

                                Супер! Надо запомнить и козырнуть
                                  +1
                                  Тот же вопрос — оптимизирующих компиляторов для микроконтроллеров нет что ли? Под gcc оптимальность и компактность кода будет связана не с количеством goto, а совершенно с другими факторами, и оптимизирует он во многих случаях лучше, чем усреднённый программист может с наскока.
                                    0
                                    По крайней мере GCC флажковую конструкцию не может развернуть в один переход, как того требует логика алгоритма, она так и остается флажковой конструкцией.
                                  0
                                  О боже я только что написал программу для AVR на с++ и не разу не использовал оператор goto и представьте она делает то что мне нужно.
                                  +1
                                  >// Сюда произойдёт переход по goto
                                  Не произойдёт. Компилятор выдаст ошибку ещё на этапе компиляции.
                                    0
                                    специально проверил в WinAVR скомпилировал однако :)
                                    0
                                    Ололо, деструктор, goto. Хреновый пример из учебника, не пишите такое в реальном проекте, т.к. будут больно бить по пальцам. В си++ центральная концепция — блоки (scope), и не нужно их вот так вот превращать в лапшу странными переходами. Перед тем, как написать goto в си++ нужно очень хорошо подумать, а зачем оно здесь.
                                    +2
                                    Полностью поддерживаю, отказываться от GOTO из-за эстетических соображений — это маразм. И если при программировании под PC все эти костыли прокатывают без проблем, то в МК левые флаги и проверки в момент сожрут всю память и такты. Надо думать головой в каждом конкретном случае, фанатизм не рулит :)

                                    З. Ы. Подписался на камменты и запасся попкорном :)
                                      0
                                      Тут как раз обсуждение идет вяловато — по сравнению с версией для PC
                                      0
                                      А в чём отличие версии для микроконтроллеров и версии для PC?
                                        0
                                        У микроконтроллеров есть существенный момент — СИЛЬНО ограниченная RAM/ROM/MIPS. Так что иногда да, на флагах стоит поэкономить. Да и на размере прогибается — тоже. Иногда 10-20 байт — критично.
                                          0
                                          Несколько иная архитектура (очень много регистров РОН, что облегчает локальные переменные). Доступ в байт из ОЗУ только через загрузку его значения в регистр и сохранение его обратно (редко бывает иначе).

                                          Крайне неглубокий стек, особенно на PIC (что то около 7 уровней чтоль). Всего десяток килобайт флеша и пара сотен байт ОЗУ.
                                          0
                                          Как много букв в доказательство того, что правильно не выбросили goto из структурных языков программирования. Правильно, да. Всяко бывает, иногда проще «назад к ассемблеру» подвинуться. Но делать из этого фетиш? — не понимаю. Вот вы говорите «программа стройней становится». А ничего, что она самой концепции начинает противоречить. Или у вас эстетическая красота кода в примате перед логической?
                                            0
                                            Главное — чтобы в пределах заданных условий она корректно выполняла алгоритм. Хотелось бы — максимально оптимально. И мечтается — чтобы код был максимально красив.
                                            А какие средства для этого используются — это уже дело десятое.
                                              0
                                              Это же просто кодекс говнокодера, уж простите мой французский. «Работает — и ладно».
                                                0
                                                «Главное» зависит от задачи. В больших проектах главное — это поддерживаемый код, который несложно расширить и пофиксить баги. А если весь код написан в таком стиле, как демонстрируется в статье, для того чтобы что-то менять придётся потратить немало времени на понимание что же на самом деле происходит, и почему если поменять что-то в одном месте, ломается в другом.
                                                  0
                                                  Если операция goto сугубо локальна, то проблемы если и возникнут, то в отдельно взятом коде.
                                                  Еще раз для тех, кто не вчитывается — я не призываю весь код фаршировать
                                                    0
                                                    Локально — не вопрос, если это необходимо. Главное, чтобы потом не возникло соблазна чуть пошире использовать, потом ещё пошире… ;)
                                                    В частных случаях-то и устройство Даффа применимо, если шибко хочется :]
                                                    0
                                                    … обилием goto! Он нужен в отдельно взятых случаях! Их немного — по пальцам одной руки пересчитать можно. Но именно там он спасает положение
                                                0
                                                Ну куда же в микроконтроллерах без goto :)

                                                /*
                                                * For PIC18 devices the low interrupt vector is found at
                                                * 00000018h. The following code will branch to the
                                                * low_interrupt_service_routine function to handle
                                                * interrupts that occur at the low vector.
                                                */

                                                #pragma code low_vector=0x18
                                                void interrupt_at_low_vector(void)
                                                {
                                                _asm GOTO low_isr _endasm
                                                }

                                                #pragma code /* return to the default code section */
                                                #pragma interruptlow low_isr
                                                void low_isr (void)
                                                {
                                                /*… */
                                                }
                                                  +1
                                                  Перепишем вариант с goto немного, XXX и YYY — какой-то код:

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

                                                  Впрыгивание L2 внутрь блока пропускает какой-то код YYY, потенциально влияющий на код после L2. Я думаю, как раз для устранения таких случаев, — а при нескольких goto может быть и больше пропущенного кода, — и стараются запретить goto вообще, чисто из профилактических соображений.

                                                    –1
                                                    В функции с множественными проверками на ошибочность выполнения проще использовать Goto для выхода из блока или вызывать исключение…… ИМХО
                                                      0
                                                      GOTO должен остаться в забвении не потому что он плох сам по себе, а потому что он плох в руках идиота. Как топор — можно и сруб смастерить, а можно и жизни лишить.
                                                        +1
                                                        можно попробовать вывести критерии, «когда точно нельзя GOTO».

                                                        Самый простой и в принципе достаточный: не используем низкоуровневый язык — пишем без GOTO.

                                                        Но для этого нужно не смешивать рассуждения для высокоуровневых и низкоуровневых языков — к сожалению, в этом посте смешение наблюдается, хотя это два разных холивара: аргументы совсем разные, да и по низкоуровневому случаю специалистов гораздо меньше, смысла спорить нет (кроме "в интернете кто-то не прав"
                                                          0
                                                          Предлагаю явно разделить случаи высокоуровневого/низкоуровневого программирования, и поспорить отдельно для высокоуровневого, готов развернуто выступить против GOTO
                                                            0
                                                            Что за копипаста предыдущей статьи?
                                                              0
                                                              Не совсем copy-paste — дело в том, что, как оказалось, не надо мне было в одной статье совмещать разные стили программирования — С++ для PC и C для микроконтроллеров. Поэтому я выделил сюда относящееся именно к микроконтроллерам. А в предыдущей статье я уже не стал удалять микроконтроллерную часть — чтобы у новых читателей не возникало недоумения от некоторых комментариев.
                                                              0
                                                              Для тех, кто следит за темой — я в конце статьи вставил плюсы и минусы использования goto. Если кто хочет добавить/исправить — пишите в ответы сюда
                                                                0
                                                                добавьте в плюсы самое важное: экономит память/такты при правильном использовании — это бесспорно, по-моему. это требования «от машины», приоритетные при низкоуровневом программировании

                                                                все остальное — приоритеты «от человека», которые сводятся к слову «сопровождаемость», здесь все сложнее.
                                                                  0
                                                                  Согласен
                                                                +2
                                                                Надуманные примеры, которые Вы применяете в пользу goto, ИМХО не нужны, в реальной жизни такого не бывает.

                                                                То, что Вы пишете про for, do, switch, что это «банальный goto, если использовать break и return» — передергивание. Испортить можно все что угодно, даже самым хорошим инструментом. Выходы из цикла через break привязаны именно к этому циклу, поэтому логику выполнения отследить легче, в отличие от оператора goto, который имеет право перейти куда угодно.

                                                                goto стали нелюбить с тех пор, как появились функции и блоки (куски кода между begin end и {}) в программах. Пусть эти блоки мало отражают действительность (реальное выполнение ассемблерного кода), но зато именно с помощью этих блоков стало возможным применить метод «разделяй и властвуй», т. е. код стал наглядным, читабельным и понятным (по крайней мере в умелых руках можно его сделать таким). То есть, не применяя goto, можно быть уверенным, что внутри блока идет линейное выполнение нужного алгоритма. Если Вы начинаете в своих программах применять goto, то Вы потенциально теряете такое удобство. Умные люди не любят goto за то, что справедливо считают — выигрыш от его использования намного меньше вреда, который оператор goto может принести.
                                                                  0
                                                                  Я в «минусах» уже отметил то, что Вы говорите,- нарушение презумпции линейности кода
                                                                    0
                                                                    Вы как-то по своему понимаете. Не линейность кода, а именно функции и блоки. Именно они позволяют структурировать программу от общего к частному, а не перемешать всё в кровавый фарш.
                                                                      0
                                                                      Ну естественно по-своему! Ведь это же я пишу :-)!
                                                                      «Линейность» в моем понимании — это отсутствие непредсказуемых скачков вперед-назад. Все случаи циклов, switch, if — это предсказуемо. А вот как раз exception в C++ — не всегда предсказуемо.
                                                                        0
                                                                        Исключения неизвестно где будут пойманы, но кроме этого они вполне даже подчиняются законам блоков, и известно что деструкторы будут позваны и т.п. Они умеют «выпрыгивать» только из блоков, но не умеют запрыгивать в середину блока, превращая код в месиво. Кроме того, исключения — необходимость, потому что нет другого способа сообщить об ошибке из конструктора. Это можно обойти и писать без исключений, но это будет уже другой подход.

                                                                        Ещё раз, имхо важны именно блоки а не «скачки вперёд-назад» или нелинейность. Блок — сущность с хорошо определёнными точками входа и выхода, и goto эту определённость уменьшает.
                                                                  0
                                                                  Реализация алгоритма, описанного в статье, без goto:
                                                                  1. void l1()
                                                                  2. {
                                                                  3.  if (b) {
                                                                  4.   l2();
                                                                  5.   B();
                                                                  6.   l3();
                                                                  7.  }
                                                                  8.  else if ( c ) {
                                                                  9.   E();
                                                                  10.  }
                                                                  11.  else {
                                                                  12.   D();
                                                                  13.   l2();
                                                                  14.  }
                                                                  15. }
                                                                  16. void l3()
                                                                  17. {
                                                                  18.  C();
                                                                  19.  l1();
                                                                  20. }
                                                                  21. // Entry point.
                                                                  22. if (a) {
                                                                  23.  A();
                                                                  24.  l3();
                                                                  25. }
                                                                  26. else {
                                                                  27.  l1();
                                                                  28. }
                                                                  * This source code was highlighted with Source Code Highlighter.


                                                                  А в целом согласен с автором.
                                                                    0
                                                                    В статье было написано — не использовать вызовы функций, обратите внимание!
                                                                  • UFO just landed and posted this here
                                                                      0
                                                                      /me в ужасе отбросил Li-Ion батарею на 3.7 вольт выдающую под 10А в КЗ.
                                                                      • UFO just landed and posted this here
                                                                          0
                                                                          ААА!!!

                                                                          /me в ужасе выбросил в окно трехфазный инвертор BLDC движка от самолетика…
                                                                    • UFO just landed and posted this here

                                                                      Only users with full accounts can post comments. Log in, please.