Comments 70
Логика программы без goto оперирует абстракцией «блок». Я вижу — блок ветвления, блок циклов, блок переключателей. Я могу в уме следить, какие из блоков будут выполняться и быстро анализировать программу.
Логика программы c goto спускает меня на уровень абстракции «инструкция». Я вынужден читать каждую инструкцию и пытаться понять, куда пойдет выполнение программы в следующей строке. Я не могу охватить взглядом весь метод, потому что он связан внутри себя миллионом безусловных переходов, передающих управление в самое неожиданное место.
В вашем первом примере, в частности, если бы блок-схема была для реального процесса, а переменные назыаны по-человечески (а не одной буквой), то логика была бы гораздо понятнее.
Логика программы c goto спускает меня на уровень абстракции «инструкция». Я вынужден читать каждую инструкцию и пытаться понять, куда пойдет выполнение программы в следующей строке. Я не могу охватить взглядом весь метод, потому что он связан внутри себя миллионом безусловных переходов, передающих управление в самое неожиданное место.
В вашем первом примере, в частности, если бы блок-схема была для реального процесса, а переменные назыаны по-человечески (а не одной буквой), то логика была бы гораздо понятнее.
А вы не лепите 20 гото, сделайте goto входной точкой перед блоком, и вы опять сможете разделять программу на блоки, а не на инструкции
при таком подходе, ваши блоки проще объявлять функциями и выходить из них return-ом. И блоки будут более явными, и не придётся каждый блок просматривать на уровне инструкций для того, чтоб понять следует ли он тому соглашению, которое «кажется принято в данном фрагменте кода у других блоков».
Здесь мой подход к программированию сильно разнится с подходом большинства: я не люблю делить программу на процедуры, которые являются всего-лишь шагами выполнения. Обычно мои прикладные приложения, работающие в режиме даэмона, делятся на три части: инициализация, основная часть, блок обработки ошибок. Инициализируясь, программа переходит в основную часть и циклично бесконечно (в завимости от того что делает программа, главная функция может просто создать пару тредов, а сама просто наблюдать за ними) выполняет одно и тоже. Если случается ошибка, выполнение переходит в третью часть, обрываются все соединения, высвобождается память, и выполняется обратный переход в основую часть.
Я пишу прикладные программы без малого лет 7 и такая архитектура себя полностью оправдала, хотя мои учителя по программированию конечно были вахЪ от моего подхода.
Я пишу прикладные программы без малого лет 7 и такая архитектура себя полностью оправдала, хотя мои учителя по программированию конечно были вахЪ от моего подхода.
Для недлинных программ такое сойдет. Но когда строк кода под тысячу… У меня программы, как правило, разбиты на много файлов и кучу функций. Плюс — reusability (или как там по аглицки :-)? ...)
Да какая разница кто сколько лет как пишет? Интереснее вопрос, общались с людьми, которые должны ваши программы поддерживать, и не предпочли ли они заново переписать вместо того чтобы с вашим подходом разбираться.
return требует больше — значительно больше! — тактов контроллера, чем простой goto. Во-первых, операция перехода и выхода. Во-вторых, работа со стеком. В-третьих, передача параметров.
В противоположность goto — одна операция.
В противоположность goto — одна операция.
inline-функции спасут
UPD: Именно для этого нужен компилятор — чтоб человеко-читаемую структуру кода развернуть в цепочку команд процессора. Обернуть неограниченные возможности goto в короткий ряд отдельных конкретных и легче понимаемых способов применения.
Вам ведь и в предыдущей теме точно так же на такой же коммент ответили, «инлайн функции» :)
Или хотите сказать для микроконтроллеров нет нормальных компиляторов, которые умеют оптимизировать? С gcc я бы не стал соревноваться на эту тему, уж больно крутым надо быть и немало времени потратить на каждый случай чтобы его переоптимизировать.
Или хотите сказать для микроконтроллеров нет нормальных компиляторов, которые умеют оптимизировать? С gcc я бы не стал соревноваться на эту тему, уж больно крутым надо быть и немало времени потратить на каждый случай чтобы его переоптимизировать.
Опять же — не весь код можно превратить в inline.
Уделать GCC для AVR (с другими не пробовал) на ассемблере раз плюнуть. Он конечно, хорошо оптимизирует по сравнению с другими Си компилерами для AVR, но нативный асмовский код все равно получается короче.
Это, кстати, одна из причин (но не единственная) из-за которой я пользуюсь goto вместо функций. Я люблю говорить «стэк не резиновый», обычно мне на это отвечают «сейчас на дворе XXI век, а ты пишешь программы, будто до сих пор под DOS'ом сидишь» (хотя я никогда не сидел под Досом, компа ещё не было). На мои слова про кол-во тактов все вообще не обращают внимания, прикрываясь тем же самым «XXI веком на дворе» и «парой наносекунд выигрыша».
Люди отвыкли писать тонкие шустрые аскетичные вещи и ленятся добавить пару строк кода, что бы сохранить мегабайт оперативной памяти. Из-за таких мы и имеем программы, жрущие по 500 МБ и это только во время инициализации.
Люди отвыкли писать тонкие шустрые аскетичные вещи и ленятся добавить пару строк кода, что бы сохранить мегабайт оперативной памяти. Из-за таких мы и имеем программы, жрущие по 500 МБ и это только во время инициализации.
Да, я лично тоже стараюсь писать программы в первую очередь оптимальные. И если для PC с таким подходом можно поспорить — из-за тогое XX1 века,- то для микроконтроллеров это очень и очень важно
Я согласен с тем, что бездумное раскидывание goto ведет к трудности чтения алгоритма.
Приведенная блок-схема условна — просто какой-то пример. Поэтому и имена там такие же :-)…
Приведенная блок-схема условна — просто какой-то пример. Поэтому и имена там такие же :-)…
А как насчет примера из учебника:
А конструктор для этой переменной не вызывался ведь?
Мне всё равно кажется, что использование GOTO без сильной необходимости разрушает принципы структурного программирования, но в одном вы правы точно — нельзя просто вычёркивать этот оператор из языка.
if (a > 0) {goto inner};
... // команды
{
X ax = X(a);
... // команды
inner:
// Сюда произойдёт переход по goto
...
// По завершении всех команд блока компилятор вызовет
// деструктор ~X() для переменной ax.
}
А конструктор для этой переменной не вызывался ведь?
Мне всё равно кажется, что использование GOTO без сильной необходимости разрушает принципы структурного программирования, но в одном вы правы точно — нельзя просто вычёркивать этот оператор из языка.
Можно и нужно. Goto не дает ничего, чего нельзя было бы достичь другими средствами, зато ухудшает восприятие.
Мы говорим о микроконтроллерах — так ведь? И о С
А если мы говорим о контроллерах, то там GOTO в порядке вещей, ибо код получается компактнее и быстрее, и в реалиях микроконтроллеров это гораздо важнее православности сионизма!
православности сионизма
Супер! Надо запомнить и козырнуть
Тот же вопрос — оптимизирующих компиляторов для микроконтроллеров нет что ли? Под gcc оптимальность и компактность кода будет связана не с количеством goto, а совершенно с другими факторами, и оптимизирует он во многих случаях лучше, чем усреднённый программист может с наскока.
О боже я только что написал программу для AVR на с++ и не разу не использовал оператор goto и представьте она делает то что мне нужно.
>// Сюда произойдёт переход по goto
Не произойдёт. Компилятор выдаст ошибку ещё на этапе компиляции.
Не произойдёт. Компилятор выдаст ошибку ещё на этапе компиляции.
Ололо, деструктор, goto. Хреновый пример из учебника, не пишите такое в реальном проекте, т.к. будут больно бить по пальцам. В си++ центральная концепция — блоки (scope), и не нужно их вот так вот превращать в лапшу странными переходами. Перед тем, как написать goto в си++ нужно очень хорошо подумать, а зачем оно здесь.
Полностью поддерживаю, отказываться от GOTO из-за эстетических соображений — это маразм. И если при программировании под PC все эти костыли прокатывают без проблем, то в МК левые флаги и проверки в момент сожрут всю память и такты. Надо думать головой в каждом конкретном случае, фанатизм не рулит :)
З. Ы. Подписался на камменты и запасся попкорном :)
З. Ы. Подписался на камменты и запасся попкорном :)
Тут как раз обсуждение идет вяловато — по сравнению с версией для PC
А в чём отличие версии для микроконтроллеров и версии для PC?
У микроконтроллеров есть существенный момент — СИЛЬНО ограниченная RAM/ROM/MIPS. Так что иногда да, на флагах стоит поэкономить. Да и на размере прогибается — тоже. Иногда 10-20 байт — критично.
Несколько иная архитектура (очень много регистров РОН, что облегчает локальные переменные). Доступ в байт из ОЗУ только через загрузку его значения в регистр и сохранение его обратно (редко бывает иначе).
Крайне неглубокий стек, особенно на PIC (что то около 7 уровней чтоль). Всего десяток килобайт флеша и пара сотен байт ОЗУ.
Крайне неглубокий стек, особенно на PIC (что то около 7 уровней чтоль). Всего десяток килобайт флеша и пара сотен байт ОЗУ.
Главное — чтобы в пределах заданных условий она корректно выполняла алгоритм. Хотелось бы — максимально оптимально. И мечтается — чтобы код был максимально красив.
А какие средства для этого используются — это уже дело десятое.
А какие средства для этого используются — это уже дело десятое.
«Главное» зависит от задачи. В больших проектах главное — это поддерживаемый код, который несложно расширить и пофиксить баги. А если весь код написан в таком стиле, как демонстрируется в статье, для того чтобы что-то менять придётся потратить немало времени на понимание что же на самом деле происходит, и почему если поменять что-то в одном месте, ломается в другом.
Если операция 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)
{
/*… */
}
/*
* 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 вообще, чисто из профилактических соображений.
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.
Но для этого нужно не смешивать рассуждения для высокоуровневых и низкоуровневых языков — к сожалению, в этом посте смешение наблюдается, хотя это два разных холивара: аргументы совсем разные, да и по низкоуровневому случаю специалистов гораздо меньше, смысла спорить нет (кроме "в интернете кто-то не прав"
Самый простой и в принципе достаточный: не используем низкоуровневый язык — пишем без GOTO.
Но для этого нужно не смешивать рассуждения для высокоуровневых и низкоуровневых языков — к сожалению, в этом посте смешение наблюдается, хотя это два разных холивара: аргументы совсем разные, да и по низкоуровневому случаю специалистов гораздо меньше, смысла спорить нет (кроме "в интернете кто-то не прав"
Предлагаю явно разделить случаи высокоуровневого/низкоуровневого программирования, и поспорить отдельно для высокоуровневого, готов развернуто выступить против GOTO
Что за копипаста предыдущей статьи?
Не совсем copy-paste — дело в том, что, как оказалось, не надо мне было в одной статье совмещать разные стили программирования — С++ для PC и C для микроконтроллеров. Поэтому я выделил сюда относящееся именно к микроконтроллерам. А в предыдущей статье я уже не стал удалять микроконтроллерную часть — чтобы у новых читателей не возникало недоумения от некоторых комментариев.
Для тех, кто следит за темой — я в конце статьи вставил плюсы и минусы использования goto. Если кто хочет добавить/исправить — пишите в ответы сюда
Надуманные примеры, которые Вы применяете в пользу goto, ИМХО не нужны, в реальной жизни такого не бывает.
То, что Вы пишете про for, do, switch, что это «банальный goto, если использовать break и return» — передергивание. Испортить можно все что угодно, даже самым хорошим инструментом. Выходы из цикла через break привязаны именно к этому циклу, поэтому логику выполнения отследить легче, в отличие от оператора goto, который имеет право перейти куда угодно.
goto стали нелюбить с тех пор, как появились функции и блоки (куски кода между begin end и {}) в программах. Пусть эти блоки мало отражают действительность (реальное выполнение ассемблерного кода), но зато именно с помощью этих блоков стало возможным применить метод «разделяй и властвуй», т. е. код стал наглядным, читабельным и понятным (по крайней мере в умелых руках можно его сделать таким). То есть, не применяя goto, можно быть уверенным, что внутри блока идет линейное выполнение нужного алгоритма. Если Вы начинаете в своих программах применять goto, то Вы потенциально теряете такое удобство. Умные люди не любят goto за то, что справедливо считают — выигрыш от его использования намного меньше вреда, который оператор goto может принести.
То, что Вы пишете про for, do, switch, что это «банальный goto, если использовать break и return» — передергивание. Испортить можно все что угодно, даже самым хорошим инструментом. Выходы из цикла через break привязаны именно к этому циклу, поэтому логику выполнения отследить легче, в отличие от оператора goto, который имеет право перейти куда угодно.
goto стали нелюбить с тех пор, как появились функции и блоки (куски кода между begin end и {}) в программах. Пусть эти блоки мало отражают действительность (реальное выполнение ассемблерного кода), но зато именно с помощью этих блоков стало возможным применить метод «разделяй и властвуй», т. е. код стал наглядным, читабельным и понятным (по крайней мере в умелых руках можно его сделать таким). То есть, не применяя goto, можно быть уверенным, что внутри блока идет линейное выполнение нужного алгоритма. Если Вы начинаете в своих программах применять goto, то Вы потенциально теряете такое удобство. Умные люди не любят goto за то, что справедливо считают — выигрыш от его использования намного меньше вреда, который оператор goto может принести.
Я в «минусах» уже отметил то, что Вы говорите,- нарушение презумпции линейности кода
Вы как-то по своему понимаете. Не линейность кода, а именно функции и блоки. Именно они позволяют структурировать программу от общего к частному, а не перемешать всё в кровавый фарш.
Ну естественно по-своему! Ведь это же я пишу :-)!
«Линейность» в моем понимании — это отсутствие непредсказуемых скачков вперед-назад. Все случаи циклов, switch, if — это предсказуемо. А вот как раз exception в C++ — не всегда предсказуемо.
«Линейность» в моем понимании — это отсутствие непредсказуемых скачков вперед-назад. Все случаи циклов, switch, if — это предсказуемо. А вот как раз exception в C++ — не всегда предсказуемо.
Исключения неизвестно где будут пойманы, но кроме этого они вполне даже подчиняются законам блоков, и известно что деструкторы будут позваны и т.п. Они умеют «выпрыгивать» только из блоков, но не умеют запрыгивать в середину блока, превращая код в месиво. Кроме того, исключения — необходимость, потому что нет другого способа сообщить об ошибке из конструктора. Это можно обойти и писать без исключений, но это будет уже другой подход.
Ещё раз, имхо важны именно блоки а не «скачки вперёд-назад» или нелинейность. Блок — сущность с хорошо определёнными точками входа и выхода, и goto эту определённость уменьшает.
Ещё раз, имхо важны именно блоки а не «скачки вперёд-назад» или нелинейность. Блок — сущность с хорошо определёнными точками входа и выхода, и goto эту определённость уменьшает.
Реализация алгоритма, описанного в статье, без goto:
А в целом согласен с автором.
- void l1()
- {
- if (b) {
- l2();
- B();
- l3();
- }
- else if ( c ) {
- E();
- }
- else {
- D();
- l2();
- }
- }
- void l3()
- {
- C();
- l1();
- }
- // Entry point.
- if (a) {
- A();
- l3();
- }
- else {
- l1();
- }
* This source code was highlighted with Source Code Highlighter.
А в целом согласен с автором.
Sign up to leave a comment.
Запретный плод GOTO сладок (версия для микроконтроллеров)!