Pull to refresh

Comments 70

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

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

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

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

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

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

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

Крайне неглубокий стек, особенно на PIC (что то около 7 уровней чтоль). Всего десяток килобайт флеша и пара сотен байт ОЗУ.
UFO just landed and posted this here
Главное — чтобы в пределах заданных условий она корректно выполняла алгоритм. Хотелось бы — максимально оптимально. И мечтается — чтобы код был максимально красив.
А какие средства для этого используются — это уже дело десятое.
UFO just landed and posted this here
«Главное» зависит от задачи. В больших проектах главное — это поддерживаемый код, который несложно расширить и пофиксить баги. А если весь код написан в таком стиле, как демонстрируется в статье, для того чтобы что-то менять придётся потратить немало времени на понимание что же на самом деле происходит, и почему если поменять что-то в одном месте, ломается в другом.
Если операция goto сугубо локальна, то проблемы если и возникнут, то в отдельно взятом коде.
Еще раз для тех, кто не вчитывается — я не призываю весь код фаршировать
Локально — не вопрос, если это необходимо. Главное, чтобы потом не возникло соблазна чуть пошире использовать, потом ещё пошире… ;)
В частных случаях-то и устройство Даффа применимо, если шибко хочется :]
… обилием goto! Он нужен в отдельно взятых случаях! Их немного — по пальцам одной руки пересчитать можно. Но именно там он спасает положение
Ну куда же в микроконтроллерах без 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)
{
/*… */
}
Перепишем вариант с 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 вообще, чисто из профилактических соображений.

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

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

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

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

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

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

Ещё раз, имхо важны именно блоки а не «скачки вперёд-назад» или нелинейность. Блок — сущность с хорошо определёнными точками входа и выхода, и goto эту определённость уменьшает.
Реализация алгоритма, описанного в статье, без 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.


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

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

Articles