Как стать автором
Обновить

Комментарии 273

goto не нужен. goto плох тем, что программу сложно понимать и отлаживать.
switch — тоже несколько спорная команда, зачастую от неё можно (и нужно) избавляться при помощи, например, полиморфизма.

Все ваши примеры опровергать не буду, достаточно последнего: оттуда можно просто убрать goto, разве нет?

int f (…)
{

if (a)

if (b)


c = 15;
return 10;
}


Ну и пост будет неполон без этого:
image
Я написал простейшую программу. Но хорошо, можно несколько усложнить пример. Посмотрите — я исправил
Ок, я понял, что рассмотрение этих надуманных функций — бессмысленно.
Давайте реальный пример, из реального приложения на языке высокого уровня рассмотрим. Без троеточий.
goto подобен ножу — малых детей лучше напугать, чтобы не пользовались, а не то натворят многочасовой отладки бед себе и другим.
Если же ты знаешь как и когда им можно пользоваться, то почему бы и нет, главное чтобы малые дети этого не увидели в вашем коде. Не то, см. выше! :)
Ну-у, это надо покопаться — если для С++. А для С — примеров куча, но для микроконтроллеров
В том-то и дело, хотелось бы увидеть пример для высокоуровневых языков C++, или C#, или Java.
У меня, конечно, не 20 лет опыта, но лет 6 уже получаю деньги за программирование (C#), и до сих пор ни разу не возникало нужды в goto.

А про системное программирование и микроконтроллеры спорить не буду, видимо, был не прав.
Вспомнил! В С++ была достаточно сложная программа, где на основе данных из XML строилась структура тестов для устройства. Причем для конкретных шагов тестов из того же XML брались параметры теста — кол-во шагов, настройка конкретных величин и т. п.
И там не раз встречались ситуации необходимости выхода из глубин нескольких вложенных циклов с обходом последующих проверок.

В другой раз был случай, когда приходилось переходить назад — и там введение do… while () выглядело весьма криво. Тогда-то я впервые и задумался о том, что иногда goto лучше других способов.
Если вы 6 лет зарабатываете программированием, наверняка сталкивались с вложенными циклами. Это именно тот момент, который заставляет меня каждый раз вспоминать про GOTO, но… нельзя. Коллеги не оценят
Кстати, в Perl можно ставить метку перед циклом и потом пользоваться last/next (break/continue) с меткой. В C++ иногда действительно не хватает.
толково придумано
Аналогично и в Java, причём метка может быть не только у цикла, но у любого блока кода.
Конечно, сталкивался. В случае, когда нужно «выпрыгнуть» из нескольких вложенных циклов, я обычно выделяю метод и использую return.

В общем, по-прежнему здесь нет ни одного примера «жизненного» кода, где goto облегчал бы жизнь.
не вижу никаких преимуществ у выделении подцикла в процедуру перед goto, кроме массы лишнего кода. А если вложенных циклов не два и не три?
«goto не нужен. goto плох тем, что программу сложно понимать и отлаживать.»
А Вы читали мой топик? Первый же пример Вас опровергает
> более чем 20-летний стаж

Вы программируете с 10 лет? Жесть… :)
С 7-ми — дальше в комментариях посмотрите
Ага, Оk, увидел :)
Ну, круто!
Сколько времени уйдет на отладку первого варианта и сколько на отладку второго?
Конечно, без goto отлаживать легче. Я в каждый момент времени знаю, в каком состоянии, в какой части цикла я нахожусь, и что меня не перекинет к чертовой матери, пока не перестанет действовать то или иное условие. В случае с таким обилием goto я такой логикой руководствоваться не могу.
А когда программа написана для микроконтроллера – это все уже становится существенно.
Не надо все в кучу. Микроконтроллер и десктоп — очень разные вещи с точки зрения политики программирования.

Я не противник goto. Но на мой взгляд, вы сильно передернули (гусары, молчать!).
Нельзя быть столь категоричным. В простых случаях, когда количество элементов в enum'е мало того, что не меняется со временем, но еще и в RFC прописано, то switch просто то, что доктор прописал: читабельно и понятно.
Насчет точек выхода из функции: ну так даже если 100500 return'ов написать, то точка выхода всё равно одна останется. В общем это дело на любителя.
Разрабы ядра linux юзают goto и их волосы мягкие и шелковистые
Я тоже не считаю goto дурным тоном, но по возможности стараюсь избегать его… ибо при отладке бывает сложно отследить что-куда прыгает :)
> Насчет точек выхода из функции: ну так даже если 100500 return'ов написать, то точка выхода всё равно одна
В машинном коде, конечно, одна, но в тексте C/C++ — нет. А программисту-то читать последний — машинный его на так интересует.
Не вижу особой разницы в читабельности между return a и goto Exit;
Я тоже, поэтому return в теле функции использую очень редко, только если без него очень уж громоздкие конструкции получаются.
Разница хотя бы в том, что про return абсолютно точно известно, что произойдёт, а про goto Exit нужно ещё смотреть, куда оно на самом деле прыгает и что делает после перехода.
Есть алгоритмы, которые без goto, во-первых, не понятны, а во-вторых не эффективны. Например, разбор последовательных протоколов.
Можно код из реальной жизни в студию?
Я когда-то писал достаточно сложный пасрер, еще не зная про boost::spirit — и там активно использовался goto. Вначале я выкручивался всяческими while… break…, но потом увидел, что код угрожающе растет и становится нечитабельным — перешел к goto и все стало значительно лучше
В системном программировании без goto очень печально.
GOTOзилла?
Все хорошо в меру.
Когда знаешь где и как можно использовать goto без вреда — нет проблем.
А когда разбираешь программу где КАЖДЫЙ условный переход сделан с GOTO (Язык — С)?
if() goto P1;
else goto p2;


Спрашиваешь «Зачем?». А в ответ — я так привык… Когда сам изучал бейсик.
Вот из-за этого лично я против данного оператора.
Да, он иногда чрезвычайно удобен. Но это именно ИНОГДА.
Мне кажется, нужно быть против таких людей типа «Я привык», «Мне так сказали», а не против инструмента языка, коим также является и goto
Согласен.
Но со свободой в действиях, которую дает goto не каждый может управиться.
Именно поэтому goto надо применять именно тогда, когда без него не обойтись.
В остальных же случаях знание об этом операторе скорее всего пойдет во вред.
Именно поэтому и есть правило «не использовать goto». А как известно, из каждого правила есть исключения. И только опыт позволяет понять когда это исключение наступило.

Я могу вспомнить всего несколько случаев, когда мне пришлось воспользоваться goto. И всего 1 раз это было оправданное использование (алгоритм был завязан на этот оператор).
А на мой взгляд goto это — зло. Его использование приводит к появлению ошибок. Сомневаюсь, что компилятор сможет оптимизировать код, напичканный goto.

Согласен, возможны редкие ситуации, когда он увеличивает читаемость. Единственный общий случай, когда я признаю оправданность его использования — выход из вложенного цикла. Собственно для этого в Java и модернизировали оператор break.
Скорее они урезали и переименовали оператор goto ;)
if с точки зрения ассемблера это частный случай goto — и он прекрасно транслируется в jne, jz или jmp в случае безусловного перехода. Уверен компилятор прекрасно знает как с этим справляться, а оптимизировать ассемблерные инструкции это вроде бы перебор.
Не столь уменьшает читаемость goto, как быдлокод. Но использовать безусловный переход имеет смысл только там, где без него не обойтись, и то с ограничениями (вменяемые имена меток и их расположения).
AFAIK, пространное эссе на тему «goto — зло» появилось в ту эпоху, когда по меткам можно было переходить не то что в циклы — в другие подпрограммы! Вот тогда — да, goto вносило огромную сложность. Сейчас же — если писать по человечески (небольшие функции, внятная нотация), то goto не внесет дополнительной сложности в понимание программы.

Вот здесь есть хороший пример грамотного применения goto: sim0nsays.livejournal.com/31460.html
Эта статья справедлива больше к С а не к С++. Ели мы говорим о С++ — то есть об ООП, то тут более актуальна как по мне вот эта статья — habrahabr.ru/blogs/cpp/112123/.
Собственно говоря, эту статью хорошо бы разбить на две — одна для С++, вторая для С. Но они будут на 80% перекрываться, я это копипаст…
НЛО прилетело и опубликовало эту надпись здесь
Проблема надумана. Я пишу программы с 1987 и как то обходился.

Меня вообще как то смущают темы «нужен ли goto» и «как написать пузырьковую сортировку» в 2011, на хабре, от людей с 20 летним стажем.
Проблема как раз в том, что из использования goto создают проблему!
А тема — «размышления на тему». Когда я привел небольшой код с goto, меня наругали и сказали, что так делать низзя! Вот я и написал свои размышления на тему
Кстати ваш пример нормальный, когда он маленький по размеру. Но вот если между оператором goto и точкой куда идет переход будет увеличиваться количество строк, скажем так полгодика будет расти, то этот гоуту станет Очень не очевидным. В принципе та можно использовать, используя те же методы выноса становящегося сложного кода в функции, но я искренне не понимаю зачем :). У меня действительно не было случая, когда я хотел бы его использовать.
Я всё детство провёл с Basic и Assembler на ZX-Spectrum. Там вообще никак без GO TO. Когда попробовал писать небольшие программы для 386 на qbasic тоже GOTO применял. Вот там было страшно самому на код смотреть через месяц. Всё-таки GOTO (Jump и т.п.) для процессора близки. Чем ниже язык программирования тем больший выйгрыш можно получить при оптимальном использовании переходов. Главное не перебарщивать, как это делал я в своё время на qbasic.
Да, помнится, на Basic-е я тоже полюблЯл goto… За что мне понравился потом С — за свою элегантность и возможность по-разному написать одно и то же — в том числе обходиться без goto :-)
На Спектруме увы C не было. А когда появился PC, то интернета не было. И книг тоже. Так что завис очень на долго в «вакуумном» информационном пространстве.
Был :)
НЛО прилетело и опубликовало эту надпись здесь
В Си есть исключения?
НЛО прилетело и опубликовало эту надпись здесь
А на чем еще драйвера писать? Не на С++ же! goto как раз активно и нужен в драйверописательстве.
НЛО прилетело и опубликовало эту надпись здесь
Где я обобщал?
НЛО прилетело и опубликовало эту надпись здесь
успокойтесь, ребята, есть в С исключения. Называется longjmp :)

и без шуток, его стоит научиться готовить.
А хорошо ли вообще использовать исключения где-либо, кроме обработки нештатных ситуаций? Сейчас при отладке программы я могу запустить дебагер «до первого Exception» (системного или пользовательского), и там разобраться в том, что случилось. Если бы исключения вызывались где-нибудь в обработке штатной ситуации, то отладить программу не удалось бы никогда. Лучше уж goto.
Вообще я goto использую только для выхода из вложенных конструкций и вместо continue, когда нельзя выполнять проверку и последействие цикла. Например:

[code]
for(list a=A;a!=null;a=a->next){
_1:

if(....){
while(!p(a)) a=a->next;
if(a!=null) goto _1;
}

}
[/code]
А в большинстве случаев хватает break и return. Особенно радует цикл for(;;), который не выполняется ни одного раза — первое же выполнение всегда утыкается в break. Лучше это, чем goto или нет — не знаю.

А вот в конструкции
[code]
if(a){
b;
if© d;
}else d;
[/code]
где d — достаточно большой код, я goto не напишу. Лучше уж код скопировать. Потому что goto вглубь фигурных скобок — категорически низзя :)
Кстати «более чем 20-летний стаж». Вы с 10 лет пишете?
С 7-ми, если быть точным :-). Еще до школы папа, который был преподаватель по информатике в ВУЗе, принес домой БКшку, а на его кафедре я с ДВКшками работал. Ну и с тех пор пошло-поехало. Через несколько лет я на ZX Spectrum драйвер на ассемблере.
Но вот когда я заикнулся на собеседовании о том же 20-летнем стаже, наступила гробовая тишина. Потом уточнили — «сколько лет вам платят за программирование?». Пришлось думать и уточнять %-)
>Через несколько лет я на ZX Spectrum драйвер на ассемблере.
Да там собственно и драйверов то как таковых не было. Обычная работа с портами ввода/вывода. Не до драйверов было. Главное чтобы хоть как-то работало и в 48 килобайт умещалось :)
В каком-то смысле да. Фактически, это была программа для вывода текста на старый добрый принтер СМ… э-э… 6337, если мне не изменяет память. Драйвер был написан на ассемблере, а использовал я его из бэйсика
Ну да. Это была некая прослойка. Она по просту была очень компактной и намного более быстродействующей.
Вряд ли это можно отнести к стажу. Я, например, рисую лет с 2-3, но не буду писать, что мой стаж как художника 20 лет. :) И компьютер у меня появился лет в 5, как и у вас. Папа базы данных в универе читал. Поэтому вам задали правильный вопрос — сколько лет вы занимаетесь программированием профессионально, т.е. получаете за это деньги.
Как бы там ни было, эффект я на девушку произвел сильный, а на ту работу я не стремился — так, решил проверить свой уровень :-).
К стажу я бы это отнес, потому что столь ранний старт и нехилое увлечение процессом дали мне большущий опыт решения всяких разных ситуаций
Мне вот 21 лет но я блия уже 22 года программирую.
Я уже в животе матери началЪ. Причем сразу с генетических алгоритмов блият.

И я, о предвидец, говорю, что ваше goto нахуй не нужно на уровень >= C
Вот уж не думал, что кого-то настолько задену упоминанием 7ми лет…
Интересно бы узнать у уважаемого borius что именно он знает о генетических алгоритмах кроме названия. И сравнивал ли он их с методом сетей Петри или нечеткой логики.
О боже, а вы что, еще что-то кроме названии знаете?

Я то не такой одаренный за 22 тяжелых года программирования я только имена смог постичь.

Вы мой герой. Скажите можно ли у вас как нить отсосать?

(П.с. а так метод сетей петри — конструкция показывающая что вы не знаете что это такое, хоть бы вики почитали и не позорились… хотя на хабре пох тут главное побольше умных слов)
Что я кроме названий знаю? Писал диссертацию с этими самыми сетями Петри — точнее говоря, разработал аналогичный аппарат, но на конечных автоматах.
Нечеткая логика тоже неплохо позволяет решать задачи оптимизации (это то, для чего генетические алгоритмы используются)
Без издевки. Можно где-нибудь посмотреть Вашу диссертацию? Интересуюсь этим вопросом.
> Нечеткая логика тоже неплохо позволяет решать задачи оптимизации
Какие именно?
знаете, а у меня точно так же, только что я на 5 лет моложе :)
Сonsidering goto harmfull is considered harmfull.

Goto нужен при обработке ошибок и выходе из вложенных циклах (хотя иногда лучше просто заранее подумать и минимализировать кол-во таких циклов). Про «выход в одном месте», если это не эмуляция try-catch, я не согласен: обычно можно всё общее свернуть в вызов функции-хелпера.

Автор молодец — разжигает холивар, ожидаю в топике появления многих точек зрения :-)
А ведь действительно, конкретно в С++ из вложенных массивов можно «выскользнуть» по throw… catch… Как-то не подумал. Допишу в тексте
Если throw-catch — это тот-же самый goto (как написано в UPD), то тем-же самым goto является и любой цикл, и любое условие — ведь на уровне ассемблера кроме goto (jmp, j*) нет ничего, позволяющего делать ветвление. Так что такое замечание, как по мне, бессмысленно.
Может, и бессмысленно, но напоминает нам, грешным, что goto есть везде, просто он политкорректно называется иначе
НЛО прилетело и опубликовало эту надпись здесь
[i]В С++ можно «выскользнуть» наружу с помощью throw… catch…[/i]
Индусский код детектед. Вы в дизасме посмотрите для начала что получается =)

Ага! В том-то и оно, что самый чистый код дает именно goto, а все остальное — на совести компилятора! Кстати, может, стоит добавить раздел с дизассемблированием разных методов и их сравнением
Для спасения из двух циклов меня иногда такой трюк выручает:

for(i=0;i<10;i++)
{
  for(j=0;j<20;j++)
  {
    if(f(i, j))
      break;
  }

  if(j<20)
    break;
}

Минусы — без лишних тормозов работает только для простых условий и требует чтобы 'j' была видима за пределами внутреннего цикла, т.е. for(int j=0;j<20;j++) не написать.

Дальше по теме — больше всего бесит switch-case, т.к. в нём не сделать break/continue для цикла, содержащего этот switch/case :) Приходится заменять на if() else if() else if() только ради того чтобы break/continue относились не к свитчу, а к циклу.
Все проще. Один флаг который может выкинуть туда, куда нужно. При этом флаг несет через название понятный смысл его существования.
Флаги еще хуже чем goto. Из религиозных соображений крайне не приемлю вынесение codeflow в данные.
Чем флаги не угодили?
Тем что это костыли.
А так же тем, что съедают регистр/стек, замедляя выполнение кода на ровном месте. Я очень не уверен, что mainstream компиляторы смогут превратить код с флагами в goto-alike конструкцию вообще без переменной. В крайнем случае можно с самим собой договориться и использовать goto только в качестве break(n).
НЛО прилетело и опубликовало эту надпись здесь
А зачем внешняя проверка?
Простите, понял.
Видел такое извращение:
if ((strncmp(command, "add ", 4) == 0 && (comm = NREAD_ADD)) ||
    (strncmp(command, "set ", 4) == 0 && (comm = NREAD_SET)) ||
    (strncmp(command, "replace ", 8) == 0 && (comm = NREAD_REPLACE))) 

PS Не мое, это из исходников memcached.
И в чем тут, позвольте спросить, извращение? я подобным образом у себя в проекте на С реализовал, правда я обернул в макрос, чтобы длину строки не считать. Единственное — плохо читается, но если разбивать на переменные, то теряем ленивую оптимизацию.
Самый, наверное, эффективный способ реализации синтаксических анализаторов
А к чему там strncmp()? В сравнении участвует константная Null-terminated строка.
Можно было обычным srtcmp() обойтись…
command имеет вид «add 1 2 ...». Поэтому сравниваются только первые 4 символа.
Я понимаю, как раз strlen(«add „) == 4, поэтому-то и можно обойтись strcmp() — после четырех символов цикл сам собой остановится.
Да, туплю…

Конечно, strcmp() — это ж сравнение строк. Очевидно, если у строк разная длина — они уже не тождественны.

Вы правы! :)
Физбаз? :)
Для меня самое зло goto в том, что с его помощью легко и непринужднно создаются циклы с несколькими входами, которые оптимизировать компилятору сущий ад. Это я со своей компиляторной колокольни :)
break туда же летит тогда и return в цикле тоже. Увы, но это тот же goto
С несколькими входами, не выходами. Выходы не мешают. С break и return полный порядок.
Циклы с несколькими входами это по-моему форменное извращение и я бы на месте компилятора ругался бы.
В спеке CPU2006 такой есть в одном из горячих циков, кстати. Я бы вообще за такое убивал.
Для меня goto появился в 8м классе вместе с «Sinclair ZX Spectrum», доставшийся со склада школьного в качестве игрушки.
Уже тогда я понимал, что он (goto) иногда чрезвычайно удобен.
Потом был VB (10-11класс) но использовал его крайне редко. видимо специфика писания в том возрасте мелких оконных «программ» по сути процедурного программирования.

а сейчас вот Ностольджы нахлынуло
Единственное место где видел goto — драйвера. И то в очень маленьких дозах.
Обычно можно обойтись чем-нибудь вроде

do
{
    ...
    if(<error>)
    {
        ...
        break;
    }
    ...
} while (false);


Против goto ничего не имею, однако и не фанатею. Если переходить, то только вниз по коду.
Это тот же goto, только ещё и нарушающий семантику.
do {} while() — это цикл, у него семантика цикла. Когда я вижу do while я ожидаю увидеть цикл. А вы его используете как goto.
Это намного хуже, чем просто поставить одну несчастную метку «error:».
Не то, чтобы я любитель этой конструкции, но это не «тот же goto». Отличие очевидно — goto может быть куда угодно и внезапно, в то время как с break всё понятно с областями видимости (scope), и про его возможность известно заранее, т.к. цикл. Это не делает while (false) красивым, тем не менее.
Когда в коде есть goto — его намного сложнее векторизовать например для параллельной обработки, и прочие виды оптимизации.

Правило «goto-зло» имхо появилось не оттого, что оно действитльно зло, а потому, что в большой команде вместо того, чтобы каждому junior developer объяснять тонкости, проще его запретить и всем будет щастье
Полностью согласен! На goto табу из-за того, чтобы начинающие лоб не расшибили — а опытные втихаря его используют, просто не афишируют
НЛО прилетело и опубликовало эту надпись здесь
Согласен, что компилятор предупредит ошибку.
А насчет того, что в Си++ goto нет места — не согласен, и я об этом уже написал — как Вы выйдете из глубин цикла, не выходя при этом из функции? Согласен, можно как-то переписать и выкрутиться, но иной раз это только лишь усложняет
Глубины цикла вообще плохо читаются.
НЛО прилетело и опубликовало эту надпись здесь
из «глубин» можно бросить исключение, которое поймать на нужном уровне, сработает в аккурат как goto, но гарантирует корректность

а вообще — слишком большие функции, особенно когда нужны такие выкрутасы с несколькими вложенными циклами — зло и надо дробить (с учетом inline — проигрыша не будет), там и без goto вникнуть будет тяжко
Ога… и сразу возникает паттерн «реализация логики работы на исключениях». Круто, че!!!
Антипаттерн скорее…
Угу. Вообще использовать исключения для выхода из вложенных циклов это стрельба из пушки по воробьям. Они вообще для другого придуманы. Выход из вложенного цикла это штатная операция, а вовсе не исключительная ситуация.
Что мешает сделать блок циклов отдельной функцией и использовать старый добрый return?
По опыту — в большинстве случаев это можно сделать, а если действительно нельзя или слишком сложно — то такой код скорее всего пишет senior и он сможет корректно использовать тот же самый goto
Так и делаю обычно, причем функции еще можно inline'ить. Просто тут некоторые советуют исключениями кидаться, что вообще форменное безобразие. Исключение — это когда уже совсем капут и программа не может дальше выполняться нормально!
Точка зрения в том, что сам факт того, что требуется применять goto или исключения для выхода из цикла — совсем безобразие и лучше лечить первопричину.
Если речь обо мне (по поводу исключений) — это просто пример решения задачи без goto. Весьма корявый, как по мне. Но — зато без goto!
Поэтому во многих coding standarts запрещают больше 4-5 отступов — нужно пользоваться функциями и выходить из них.
Мое мнение:
goto нужен для оптимизации в кое каких местах, это очевидно. Читаемости кода с моей точки зрения (как человек мыслящий критериями ООП) он не очень способствует.

Поэтому вывод:
1) Использовать goto в рамке сложной программы нельзя.
2) Использование goto выгодно когда требуется выполнить очень много однообразных операций, не выходящие за пределы одной процедуры, то есть в той процедуре где используется goto уже не должно вызываться других процедур.
if(a==1)
{
  A();
  C();
}
while(b!=0 && c!=1)
{
  if(b==0)
  {
      D();
  }
  B();
  C();
}
E();

А где сказано, что A, B, C, D, E — это функции??? Это просто какие-то операции! И вполне возможно, что они используют массулокальных переменных! Обратите на это внимание!
Только не надо мне предлагать в таком случае создание класса для их хранения…
Функция с такой извращенной логикой и ещё с блоками кода, которые используют массу локальных переменных явно нуждается в рефакторинге.
А я честно и сразу написал в тексте, что не знаю, кому нужен такой бред!
ДА и вообще — вот есть такая постановка задачи — и все тут! Надо реализовать! И как по мне — goto с этим справится лучше всего
По вашему алгоритму, A,B,C и D это блоки операций, логичнее всего их выделить именно в функции, если они требуют каких то переменных для своей работы — передайте эти переменыне как аргументы. Выше написанный код действительно легко читается.
Возможно, что в качестве аргументов придется передавать все локальные переменные, да еще и по ссылке. Неэффективно…
Отож, и это раз.
Второе — тут многие (например, тут) goto заменяют copy-past — и это называется грамотным программированием?..
Вы рассуждаете о неэффективности несуществующей программы?
Некто Кнут говорил, что «Преждевременная оптимизация — корень всех зол».

Ну и есть же inline в этом вашем С++, если уж действительно оказалось неэффективно.
inline, насколько я помню, в plain-c отсутствует, кроме того inline — это РЕКОМЕНДАЦИЯ компилятору заинлайнить функцию, а не приказ.

Про Кнута, меня вот в последнее время веселит мания людей упоминать эту фразу. Оптимизация это когда изначально код хороший и четкий, но мы делаем из него быдлокод, чтобы оно быстрее работало. А сейчас это стали употреблять во всех случаях где надо и не надо. Допустим по алгоритму очевидно, что нужен контейнер с временем доступа O(1) и возможностью расширения. Ничто не мешает использовать vector, но люди воткнут list прикрываясь тем, что «преждевременная оптимизация — корень всех зол»

P.S. Лично я не согласен с конкретным примером топикстартера по использованию goto в данном случае.
Только зачем прямое сравнение с 0 и 1, когда в алгоритме этого нет.
Видимо, поэтому tzlom и не заметил, что
while( b && !c )
{
if( !b )… работать не будет.
Отож! Я тоже почувствовал, что что-то не то в этом варианте, но не вникал. Хотя стоило бы…
А в варианте с goto — и вникать не надо. Логика работы очевидна!
«Не то» здесь из-за кривой записи условий, а не отсутствия goto.

Пока код крошечный, ему и goto не страшен, потому что хорошо виден. А вот когда Вы развернёте все ваши A, B, C и так далее, да приправите «массой локальных переменных», тогда Вы и получите истинное пиршество для багов.
Вы некорректно привели пример, потому что скрыли в нём всё, что собственно и делает программу с goto нечитаемой.
Резонно, впрочем исправление не сложно
while(!(!b && c))
А почему не while(b || !c)?
на мой взгляд так легче понять что именно проверяет условие
я исходил из того что есть условие выхода, а в остальных случаях выполняется цикл
А что вам мешает сделать их функциями? GOTO?
Да и замечу что дублируется только С так что проблема высосана из пальца
P.S.
очевидно что этот код гораздо читабельнее чем ваши гото
P.P.S.
zxm тоже правильные вещи пишет
Не хотите класс — создайте структуру. Масса локальных переменных — зло не меньшее, чем goto.
Если это — программа для микроконтроллера, то так и недо писать — для них правила другие.
А в обычных приложениях как данные, так и действия просто необходимо организовывать, тогда контролировать код будет намного легче, и костыли вроде goto не потребуются.
Я перестал использовать goto где-то в 1988 году и с тех пор желания вставить его куда-нибудь не возникало. Кстити, работал я тогда ещё на ЭВМ ЕС-1055.
Не поленились разобрать первый пример =), завидую.
Если вам кажется, что goto может улучшить код, то вы накосячили с архитектурой. В коротком коде еще можно изящно воспользоваться им, но если код достаточно сложен — ну его нафиг.
Что-то в последнее время большой приток алдековцев на Хабре :D

Хорошая статья!
Эх-х, давно я работал с VHDL — к сожалению, дальше теории дело не пошло. Попробовал на практике и ужаснулся — решил пользоваться ATmega и кучей обвязки…
В прекрасной книге Макконнела «Совершенный код» есть почти целая глава, посвященная goto. Там очень доступно раскрываются плюсы и минусы. Перескажу её в трех тезисах:

1. Основная задача программиста — снижение сложности кода, который он пишет. Это важнее эффективности, скорости, ресурсоемкости и т.д. так как из понятного кода всегда можно сделать быстрый или малопотребляющий ресурсы, а наоборот — нет.

2. Оператор goto КАК ПРАВИЛО увеличивает сложность кода. Переход непойми-когда непойми-куда непойми-зачем.

3. В тех редких случаях, когда goto уменьшает сложность — его МОЖНО и НУЖНО использовать. Но нужно быть четко уверенным, что его применения оправдано.

Как и любой другой инструмент, goto бывает в тему, а бывает нет. Головой думать надо.
Сталкивался с доводкой под современные нужды программ написанных на старых реализациях fortran-а. Чёрт ногу сломит в старых конструкциях, когда использование GOTO было явным (кто видел, тот поймёт. Кто не видел — советую посмотреть занимательные / 15 do 21 сТЕЛО 21 GOTO 15 /).

Сейчас это (вредность) конечно не так актуальна, и я соглашусь с автором — подчас использовать GOTO проще и информативнее.
Вот из за таких фортрановских прог Дейкстра и начал свою войну с goto
Использовать goto — это как переходить дорогу в неправильном месте. Вроде можно успеть, но шанс быть сбитым значительно выше.
Код функции, цикла можно свернуть и оставить короткий кометарий. Блок с goto как сворачивать непонятно. И не понятно, какой уровень сложности там в конце концов окажется. В конце коцов человек возьмет карандаш, бумагу и начнет чертить для себя схему работы этого goto коммутатора.
goto — игра в наперсток.
P.S. За статью все равно спасибо. Существует много догм в программировании, которые надо пересматривать.
В далекие 90-е моя учительница по Basic говорила что «чем больше операторов GOTO в программе тем ниже квалификация программиста».
А до нее — Эдсгер Вибе Дейкстра
В смысле, тем низкоуровневее он программирует? :)
Чем она предлагала пользоваться?
В С++ ситуацию я не готов комментировать с достаточным основанием, но все же стоит сначала понять, что алгоритм должен делать и лишь потом переходить к его кодированию. Это вообще, общий совет. Сначала надо понять, что ты делаешь, а вот тогда и выяснится, что высокоуровневые примитивы лучше описывают задачу. Я не беру ситуацию низкоуровневой оптимизации, которая как понятно никогда не должна быть преждевременной.

В современных язык очень не любят goto из очень простых соображений, высокоуровневые примитивы предсказуемы. Точка входа в for, while, if предсказуема и понятна. Точка входа в for это строчка которая находится перед этим самым циклом. Да, внутри цикла переходы будут посложнее, но опять же и тут это предсказуемые и однотипные переходы. В случае с метками, никогда не поймешь откуда произошел переход на нее, пока не просмотришь ВЕСЬ код. Теряется свойство локальности, инкапсуляция кода.

Аналогичная беда есть с эксепшенами, но и там как бы контрол флоу предсказуем относительно, хоть и требует некоторого навыка, ибо он неявен. Но все равно в целом контроль над переходами остается. В случае с goto же получается полная каша, глядя на метку невозможно понять откуда на нее попробует код перескочить.
То-ва-ри-щи! Ну кто — кто призывает Вас лепить goto повсюду по поводу и без повода?!?!? Использование goto в С и тем более С++ — достаточно редкая и даже исключительная ситуация! Но такая ситуация встречается! И вот эти ситуации надо четко выделять, рассказывать начинающим и учить их: «goto использовать крайне не рекомендуется. Но есть несколько ситуаций, где он нужен — это 1)… 2)… и 3)… В других случаях его лучше избегать, поскольку можно заменить 1)… 2)… и 3)… ».
Вот и все!
В языках с перегружаемыми функциями (в java вон вообще по умолчанию функции виртуальные), это ощущение контроля тут же теряется — при вызове функции далеко не всегда известно, куда понесёт и что оттуда вернётся. Да и сама суть полиморфизма в этом, так что не факт что именно это основная проблема.
Да, что хорошо, про Яву мне говорить попроще. Действительно вызов функции может привести к очень не очевидным действиям, особенно, если у нас вызов идет через динамический контейнер (пикоконтейнер, например), еще и используя reflection. Но, эту сложность и хитрость специальным образом обертывают в предсказуемые высокоуровневые концепции, это либо библиотека-контейнер, которой надо довольно долго учится и понимать ее, либо просто стандартные концепции Явы. Это не тот беспредел, который вносит goto. Это высокоуровневый контракт, в который и выносят всю сложность этого типа. У exceptions есть схожие проблемы. Как раз основная идеология, это собрать все сложные места и вынести их в небольшое ядро и обеспечить некой предсказуемой идеологией. С goto проблема в том, что в каждом месте, где оно будет использоваться, это будет свой способ, за этим будет своя логика, свои переходы итп.

Аналогичный подход используется в многопоточных штуках всяких, когда всю многопоточную специфику уносят в ядро, где обеспечивают корректные передачи объектов, понятные контракты итп. При этом отсальной код пользуясь некоторым набором высокоуровневых правил может не заботится изза многопоточки.
Не понимаю весь вводный опус по поводу языка ассемблера. Если говорить честно, то кроме условных и безусловных переходов там вообще нет функций управления потоком выполнения (разве что call и retn). Никаких готовых циклов и if. Я к тому, что если кодить на асме, то использование jXX (прямого аналога goto/if+goto) — единственная возможность реализации условий и прочего.
А так да, если использовать goto умерено и не прыгать в центр блоков кода — то это может гораздо уменьшить объём кода, увеличить читаемость и как следствие уменьшить количество багов. Особенно часто goto хорош если в конце процедуры нужно выполнить несколько заключительных действий (закрыть файлы/освободить память) и только после этого выйти, а точек выхода из самого алгоритма несколько (примерно как в последнем примере). Можно использовать для этого exception'ы, но на мой взгляд такие извращения только для тех языков, в которых нет goto (например Python).
НЛО прилетело и опубликовало эту надпись здесь
Пример изначально надуманный.

Как сказал один мой знакомый:
Автор нарисовал блок-схему с конкретными хомутами, и показал, что гоу ту применим в таких случаях. имхо, если в приложении дошли до таких изворотов в логике, то там уже пох на всё и можно писать все что угодно, goto тоже )
Я вам могу привести реальный пример на си. Как вы знаете, в большинстве случаев сишные функции в качестве результата возвращают ноль или код ошибки. Допустим вам надо сделать последовательный вызов 10 функций, причем каждая последующая вызывается только после успешного окончания предыдущей. В случае неуспеха надо допустим вернуть номер упавшей функции по порядку.
Код без goto
if(!func1(...))
{
  if(!func2(...))
  {
     .....
   }
   else
   {
    code = 2
   }
}
else
{
code = 1
}


Случай с гото
if(func1(...))
{
code = 1;
goto exit;
}
if(func2(...))
{
code = 2;
goto exit
}
...
exit:
return code;


Также учтите, что в exit может стоять освобождение выделенных в куче ресурсов, а с множеством return'ов вам это освобождение придется вписывать везде.

В общем я за гото именно в таких случаях, поскольку он упрощает код и облегчает его понимание. Но безусловно я против, когда гото используется просто потому, что «программист» не может описать свою задачу алгоритмически. И повторюсь, это для си, в плюсах естественно все по-другому.
Можно последовательные вызовы функций унести в отдельную функцию, использовать return, а выделение и чистку ресурсов сделать в обертке.
Да, вы правы, можно и в обертке, но только это лишнее усложнение кода, поскольку создается функция, которая в принципе не нужна. Ну и ко всему прочему вызов функции все-таки немного помедленнее безусловного перехода. Это как в тройном вложенном цикле, можно тупо goto, а можно наворотить кучу флагов и ненужных проверок.
inline-вызов функции у хорошего компилятора никак не медленнее чем обычный переход ;)
Охх, «лишнее усложнение кода» — это как раз лапшевидные goto. Главное это всё же чтобы код легко поддерживался, т.е. легко читался. Вызов функции совсем не обязательно медленнее, уже очень давно компилятор нужно отдельно заставлять, чтобы он не инлайнил функции. И это ещё умудриться надо заставить, потому что в отдельных случаях эта сволочь ;) заинлайнит функции, которые казалось бы вообще нельзя (link-time optimization). Вообще, говорить что-то о производительности без тестов (к тому же грамотных) — гиблое дело.
Это как раз правильный C++ way, автор же больше на си напирает :)
if( func1(...) )
{
    code = 1;
}
else if( func2(...) )
{
    code = 2;
}
...
return code;
А если между вызовами нужно делать еще какие-то операции? например печатать в лог?
if( !code && func1(...) )
{
    code = 1;
}
if( !code && func2(...) )
{
    code = 2;
}
...
return code;


А вообще, для этого в с++ есть исключения
Ах, простите, вы про си писали…
Ну вообще я написал, что это С.

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

Все жа наиболее равнозначной заменой будет функция-обертка, но это уже лишнее усложнение.
Для си я с вами согласен, невнимательно прочитал…

Для с++ лучше все-таки обходится без goto (если это не усложняет восприятие чрезмерно), потому как деструкторы и конструкторы. Бывают, конечно, случаи… но редко.
code = 0;
if (!func1(...)){
code =1;
}

if (!code){
if (!func2(...)){
code =2;
}
}

if (!code){
if (!func3(...)){
code =3;
}
}

...

return code;
Куча лишних ненужных проверок
Никто не говорит, что в высокоуровневых языках надо использовать goto. Для конкретно моего случая там есть исключения и функции возвращают обычно результат, а не код ошибки. Но на plain-c это по-моему наиболее удобный и быстрый способ.
Вы привели пример падения читаемости кода без использования goto, я показал, как можно обойтись без него и сохранить читаемость кода, приближенную к исходной. А если заменить название переменной с code на error, то еще и по фрагменту текста станет понятно, что мы не вызываем func_n+1, если у нас уже случилась ошибка. Вариант с goto не объясняет нам, почему мы делаем переход.
Если говорить о ненужности проверок с точки зрения скорости выполнения кода — без контекста это будет преждевременной оптимизацией.
Вопрос, а зачем это было делать, если goto уже обеспечивает отличную читаемость да и в принципе должен быть оптимальным по количеству инструкций? Из каких-то религиозных соображений? Так вот про этим самые религиозные соображения и говорил ТС и я с ним согласен.
Тут скорее другое. Меня учили программировать без goto, но этот оператор рвотного рефлекса у меня не вызывает.
Если вариант без goto будет страшен как смертный грех — поставить goto рука не дрогнет. Описанную же Вами задачу я бы, скорее всего, решил так как написал, т.е. с проверку на возникновение ранее ошибки.
Кроме того, если между операциями надо что-то делать, причем только если операция была успешной, то у вас добавится еще куча условий. И ради чего все это? Ради того, что какой-то дядя сказал, что goto плохо и не надо его использовать вообще никогда?
В моем примере if резделены, т.е. один на проверку отсутствия ошибки, а вложенный обрабатывает возврат и текущей функции. С

если между операциями надо что-то делать, причем только если операция была успешной


проблем не вижу.

И я не говорю про то, что goto следует выбросить из языка. Я говорю про то, что в конкретном, приведенном Вами примере можно обойтись без goto и не потерять в производительности.
if(!func1(...))
{
return 1;

}
if(!func2(...))
{
return 2;
}

return из глубины функции это тот же слегка замаскированный goto. И даже в чём-то хуже — например, поставить брейкпоинт на выходе из функции оказывается нетривиально и чревато ошибками.

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

А разбивание на под задачи это даже хорошо чем куча не управляемых меток.
За весь не большой опыт не разу не было случая когда не то что стояла острая необходимость в goto, а даже малейшего намека на это. Я о нем даже и не вспоминаю.
О, вот оно и есть то самое! Полностью согласен с комментарием!
Как правило такой код можно разбить на несколько логических блоков типа инициализация, основная работа, освобождение ресурсов и эти логические блоки реализовать в виде отдельных функций. Все лучше чем ваша простыня.
Ага, и каждый из таких логических блоков будет в качестве результата возвращать код ошибки. Таков уж стиль С и такие ситуации в С -программировании встречаются весьма и весьма часто.

А по поводу освобождения ресурсов. Где-то слышал мнение о том, что в идеале программа на С должна освобождать выделенную память в том же логическом блоке, в котором она была выделена. В противном случае вы можете долго и непринужденно ловить утечки памяти. Ну исключением из этого правила являются разве что аллокаторы, но это отдельная песня. Это в java можно создать объект и вернуть ссылку на него, авось GC потом разберется. А в си надеяться на то, что вызывающий код освободит выделенную в данной функции память не стоит.
>> Ага, и каждый из таких логических блоков будет в качестве результата возвращать код ошибки
И в чем проблема?

>> А в си надеяться на то, что вызывающий код освободит выделенную в данной функции память не стоит.
Ересь какая-то. Что значит не стоит надеятся? Вы же не в рулетку играете. Есть контракт, его несоблюдение это ошибка, да.
Интересно, у Кернигана была хоть маленькая надежда что вы free вызовете после malloc? :)
1. А то, что в вышестоящей функции вам так же придется обрабатывать эти коды после каждого блока

2. free и malloc это низкоуровневые инструменты. Это и есть те самые аллокаторы, о которых я писал, но это не значит, что каждая вторая функция должна выделять память и возвращать указатель на нее. Вот это уже будет похлеще goto, программа вроде работает и правильно работает, но блин память потихоньку куда-то девается

3. А про Кернигана это вообще смешно. Как будто у создателя молотка есть надежда, что пользователь этого молотка не треснет себе по пальцам? Однако же при работе с молотком есть такая вещь как техника безопасности, в которой явно указывается, что по пальцам себе бить не стоит. Не, вы конечно можете это сделать, но это будут уже сугубо ваши проблемы.
Всегда для этого делал:
do {

if( func1(...) ) {
code = 1;
break;
}

if( func2(...) ) {
code = 2;
break;
}

...
} while( 0 );

И горя не знал ;)
А если у Вас внутри будет вложенная конструкция? Там уже нужно несколько break
Не без этого. Но всё же лично я предпочту в 90% написать без goto. Почему? — этот код может потом править другой человек, где уверенность, что он также учтёт все возможные проблемы?
Да, алгоритм надуманный. НО — может случиться в реальной жизни! На нем я показал, как goto-боязнь заставляет делать сложный и малопонятный код.
А алгоритм был придуман, кстати, под воздействием температуры 38.3…
google и qt не используют исключения.
и?

и ничего.
просто одним инструмент подходит, другим нет.

также и с goto.

можно/нельзя — это не тот вопрос, который нужно задавать.

оправдано/не оправдано использование в каждом конкретном случае — вот это правильный вопрос.
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 ();


а вот так разве не лучше?

void task()
{
char a, b, c;

for (a = 0; a < 10; ++a)
{
for (b = 0; b < a; ++b)
{
if (!c) return;
}
for (b = 10; b < 15; ++b)
{
d ();
}
}
}
...

task();
e ();
А где же выполнение e() перед выходом?
сразу после task();
Да, разобрался, увидел.
Как по мне — мой вариант читабельнее и вопросов не создает
Так хуже, непонятнее и фрагментарнее. Особенно если вспомогательной функции приходится передавать мешок параметров.
Ага, и особенно надо помнить о конструкторах копирования, правах const и тому подобном…
А теперь посмотрим на хитровывернутый алгоритм. Представление не имею что это за бред – но его надо реализовать.


Что значит «представления не имею, что это за бред»? А как же анализ ТЗ?
Шутка юмора. И намек на то, что не нужно рассматривать данный пример на реальность, рефакторинг и т. п. А взять его как данность — этот алгоритм — и попробовать реализовать
«А то, что не-использование goto не застрахует Вас от неправильно написанной программы. Так же и применение его не гарантирует, что программа будет сыпаться. Нужно писать программы с умом и не делать таких элементарных глюков.»

Из этих строчек у меня складывается впечатление, что статья ни о чём. То что вы пишите, и так понятно.
Это не всем понятно. goto is evil — это догма. Для многих эта догма является непререкаемой истиной, вон посмотрите сколько таких в комментариях.
Эта статья напоминает, что программирование должно быть основано на логике, а не на догмах.
Begin
L1:
If (goto нужен) goto L2
L2:
If (goto не нужен) goto L1
End.
Хорошая статья, для «новичков в этом мире программирования» как я, это хороший пример что goto всё-равно лучше не использовать, но из тысячи функций, в одной может оказаться так что goto будет лучше и красивее и даже читабельнее.
Но это крайне редкий случай.
Программируете с 7 лет? Авторитееет!!! (я бы вот постеснялся так считать сколько я программирую). В языках высокого уровня goto — ересь и она должна быть уничтожена вместе с носителем.
В вашем примере с циклами всё решается:

char a, b, c;

for (a = 0; a < 10; ++a)
{
for (b = 0; b < a && c; ++b) {}

if (!c) { break; }

for (b = 10; b < 15; ++b)
{
d ();
}
}

e ();

Но вообще такое попадалось максимум раза 3, но решал всегда без goto. Лично я его не использовал, так как нигде не видел в нём особой нужды.
Вспомнил интересную историю связанную с goto. После того, как Дейкстра опубликовал свою статью Go To Statement Considered Harmful один из разработчиков фортрана Лоренс Кларк предложил, раз уж пользоваться goto плохо, ввести новый оператор COMEFROM. В операторе COMEFROM указывалась метка при достижении которой управление должно переходить на соответствующий оператор COMEFROM.
www.fortran.com/fortran/come_from.html
Знаете, пришлось недавно по учёбе писать лабы для некоего контроллера Мега (не знаю, что за девайс такой и чьего он производства). Но у него свой ассемблер, в котором нет даже GOTO. Программа — один большой цикл и из всех условных команд есть только SET и RST (устанавливающие и сбрасывающие определённый регистр только если значение регистра-аккумулятора истинно).
Я молил небо о том, чтобы оно мне ниспослало ХОТЯ БЫ goto )))
может ATmega?
Нет, это не Atmel, у тех вполне вменяемый ассемблер (а на AVR32 можно и на C и на C++ писать).
в ассемблере не было безусловных переходов? Подробнее.
Да, как-то фантастически звучит… Может, речь о DPS-контроллерах?
Хм… моих познаний явно не достаточно, или вы, в свою очередь, имели ввиду DSP? Но на сколько я знаю, там тоже есть переходы и ассемблер под них достаточно сложен
Приходилось писать под DSP Texas Intruments 6200. Жестокая вещь. Jmp там, конечно, есть (более того — нет ни call, ни return — все приходится писать через jmp). Но проблема в том, что этот jmp срабатывает только через 5 команд, после того, как он написан! Т.е. пишется

jmp X1
cmd1
cmd2
cmd3
cmd4; только в этот момент сработал jmp
X2:

Зато там выполнение любой команды можно сделать условным. Есть несколько регистров (3 или 5), которые можно использовать в кажестве флажка — выполнять команду (в том числе, jmp) только при условии, что регистр равен нулю или при условии, что он нулю не равен. Очень эффектно выглядят циклы, начинающиеся с jmp на себя:

X1: [B0] jmp X1
sub B0,1,B0


Если добавить, что там 8 логических устройств, работающих параллельно, но команды для них надо указывать явно (т.е. одна команда может занимать до 8 строчек кода), все становится еще веселее. К сожалению, ощутить это дело в полной мере пока не удалось.
Мда-а, хорошо, что я еще не программировал под них! Банальный ATmega — все просто!
Да, это дзен.
Ой-й, ну да, DSP…
Там какой-то учебный контроллер. 16 дискретные входов, 16 выходов, аналоговые входы, регистры, таймеры и ячейка-аккумулятор, именно с которой и работают почти все команды. Например AND X00 производит логическое И аккумулятора с дискретным входом 0 и помещает результат в аккумулятор. Немножко расширяет сознание, разбалованное питоном с его вкусностями :-)
Ну по описанию это мелкие AVR\PIC-подобные контроллеры, но что б у них не было goto…
Кстати, табу на goto появилось вроде бы в рамках структурного программирования. Т.е. программирования доказательного.
А как насчет comefrom? :)
За последние десять лет ни разу не писал goto, вот как с TurboBasic'а ушел, так и не пользуюсь =)
Это ваше goto напоминает ссылки в законодательных актах. Типа пункт 1.3 — см постановление №13747 от 13.10.2003, а там еще куча таких же ссылок. И вроде хотел по законодательству посмотреть — что да как делать, а в итоге тратишь час на разбирание этой лапши. Юристам, которые в договорах делают ссылки между пунктами — руки бы поотрывал.

Все хорошее хорошо в меру. И если использование оправдано, то почему нет, а если оно только усложняет понимание и весь процесс — то в топку такие штуки. KISS.
Полностью согласен с автором. Даже в java не смотря на все его многочисленные запреты, в угоду стабильности, сохранились метки и операторы очень напоминающие GOTO. Вот только объявлять и использовать их можно только внутри одной функции, что с моей точки зрения абсолютно правильно. А вообще чтобы избавиться от предрассудков всем рекомендую немного по программировать на Ассемблере.
Так то оно так. Когда я писал на Си драйвер, метки с goto сами собой как-то напрашивалиь :) Программируя на java ужн года 2 так и не разу не было случая, что бы нужен был goto. К примеру в случае в выходом из вложенных циклов: обычно я такие штуки в отдельный метод выношу.
А вот в Tcl команды goto нету. Как, впрочем, и в лиспах всевозможных, насколько правильно я помню.
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
Слышал мнение, что оптимизатор компилятора плохо работает с goto. Кто-то может прокомментировать?
Бывает, что компилятор «разворачивает» некоторые циклы в целях оптимизации, или меняет порядок команд без изменения логики работы. Если в этом месте присутствует goto и метки для него, то он ничего менять не станет, ибо логика может нарушится. В большинстве случаев компиляторы справляются и никаких проблем не возникает.
Холивар с goto же Виртом был разведен? Ну дак там же и цели были другие — обучение студентов культуре программирования.
Дейкстрой
Использую goto только для выхода из вложенных циклов, в остальном стараюсь обходиться без него.
Обычно можно такой код вынести в подпрограмму. Попробуйте. ИМХО — это решение еще более читабельно, чем goto. Флажки — вот это точно полный бред.
Когда после goto ещё много всего, то так и делаю. А ради пары строк кода плодить новые функции с дурацкими именами (просто осмысленные названия таким функциям тяжело придумать) не очень то хочется.
Мне кажется, что полезность goto в мире C++ значительно ниже, чем в мире C.

1. Дело даже не в пресловутых исключениях. Например, я не считаю, что использование исключений для выхода из вложенного цикла оправданно. Думаю, что killer-feature, делающая goto малополезной это автоматический вызов деструкторов созданных объектов.

Рассмотрим один из примеров использования goto в языке C.

void f()
{
  // выделение ресурсов

  // goto ... goto ... goto ...
EXIT:
  // освобождение ресурсов
}

Подобный код в C++ используется реже, благодаря идиоме RAII (Resource Acquisition Is Initialization). Этот код в C++ скорее всего будет выглядеть так:

void f()
{
  // инициализация объектов выделенными ресурсами
  // return ... return ... return ...
}


2. Полагаю, что несмотря ни на что goto в программах на C++ может встречаться, например, в автоматически сгенерированном коде. Однако ваши примеры с «флажками» и без выглядят переусложнёнными, и немного надуманными. Мне кажется, что подобный код в промышленном коде нежелателен, так как не читается «влёт». Если есть возможность, его стоило бы переписать, и потом уже оценивать мощь goto.

3. Широко распространено мнение, что выход из вложенного цикла является хорошим кандидатом на использование goto. Возможно и так. Однако в моей практике выделение новых функций улучшало читабельность, и облегчало дальнейшее сопровождение программы.
Мда-а, вижу зря я в одной статье смешал С (ориентация на микроконтроллеры) и С++ (ориентация на PC). Напишу я, наверное, отдельную статью именно для микроконтроллеров. Может, в данной статье удалить все упоминания С?.. Не знаю, тогда другим читающим будут непонятны комментарии.
Дело в том, что между си и си++ сейчас огромная увеличивающаяся пропасть.
А микроконтроллеры и прочее embedded это вообще отдельная тема, на мой взгляд мешать всё в одну кучу это как мешать веб-разработку и программирование на асме, совершенно разные подходы и устои.
Да-да-да, я это уже усёк. И все-таки основную статью переписывать не буду — просто вначале поставлю ссылку на версию для микроконтроллеров.
НЛО прилетело и опубликовало эту надпись здесь
ага, потому все игры просто молча падают, вместо того чтобы исключение обработать )
НЛО прилетело и опубликовало эту надпись здесь
У вас явно проблемы либо с алгоритмическим мышлением либо с умением читать/писать блок-схемы. Программа из первого «контрпримера», та которая без GOTO, написана с ошибкой, то есть вообще не правильно реализует алгоритм с блок-схемы, она не эквивалентна той что с GOTO. Правильно этот алгоритм реализуется так:

if a:
    A()
    C()

while b:
    B()
    C()
    if not b:
        if not c:
            D()
            B()
            C()
        else:
            E()
            break


Это ГОРАЗДО нагляднее чем ваша хрень с GOTO, которую и программой то назвать нельзя. Если хотите писать такие «программы», то пишите на ассемблере и не позорьте языки высокого уровня.

На счет GOTO — я сам его довольно часто использую в C, в ситуации указанной в комментарии tenzink, но то что вы несете про читаемость кода это бред. GOTO разумно стоит использовать только в простейших конструкциях, где он не несет усложнения структуры и не пересекается с операторами ветвления.
и еще немного проще:
if a
A
C

while b or not c
if not b
D
B
C

E


но протестую против совета «пишите на ассемблере» — как раз из-за низкоуровневости еще больше требования к умению писать читабельный код, поэтому лучше сначала оттачивать логику, скажем, на Паскале, а затем уже асм — чтобы было понимание, что такое понятный код, и стремление и ассемблерный код таким же по максимуму сохранять — и простыми средствами, и с помощью макроассемблера.
сорри, понадеялся, что оставляет форматирование

if a
	A
	C

while b or not c 
	if not b
		D
	B
	C

E
И правда, всё верно :-).
А вот и неверно — Вы дважды написали действие C — т. е. метод copy-past.
Ой-ой, от Паскаля к Ассемблеру?..
Я в свое время вначале писал код на Си — оттачивал алгоритм. Потом его переписывал на Ассемблере, оставляя сишный код как комментарий! А потом делал оптимизацию на Ассемблере (с учетом конвейера).
А между Паскалем и Ассемблером пропасть как между Си и Бейсиком
я же не про написание конкретного кода, а про оттачивание техники:

1) сначала на самом простом, но не ущербном, языке привыкаем писать просто и понятно — прививаем вкус;

2) затем, взявшись за более мощный, но низкоуровневый, стараемся писать максимально просто и понятно, т.к. уже знаем, к чему стремиться.
Скажем так, иногда goto позволяет решить задачу проще. Но в том же C++ я не хочу думать, а не приведёт ли вызов goto к тому, что будет пропущен конструктор. Поэтому для своей же головы проще без него. По этой же причине не объявляют переменные внутри switch.
Почему не объявляют — сделал область видимости внутри case и вперёд.
case 0:
{
  int i;
}


Не шибко красиво, конечно, но что уж поделаешь — в самой конструкции switch явно просматриваются сишные а то и ассемблерные корни…
Ну да, не уточнил. В таком виде — да, конечно — нередко такие скобки появляются. Главное не забыть break ;)
Я пробовал — компилятор (Visual Studio 2008) не разрешил мне сделать goto в обход конструктора. А вот в обход деструктора можно — потому что деструктор вызвался все равно :-)
Да, GCC тоже не дал. Видимо слишком давно на эту проблему смотрел. Но не знаю, всё равно goto нарушает блочную структуру функции/метода. Наверное в этом моя наибольшая претензия к данному оператору. Во всех остальных случаях переход осуществляется к границе блока, а тут «бац» — куда угодно. Не спорю, что иногда это полезно и без goto приходится городить страшные конструкции. Но это скорее недостаток синтаксиса C++ (хоть я его и люблю), в Perl и, как мне сказали выше, в Java возможен более гибкий переход.
Использовать goto в С++ — всё-равно, что использовать switch в PERL.
Использовать goto в С — всё-равно, что использовать jmp в ASM.
А в FoxPro и Visual FoxPro нет оператора безусловного перехода (оператор goto там выполняет другую роль)
Кстати, тут:

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

можно обойтись без флагов:

for (b = 0; b < a; ++b)
{
if (!c)
break;
}
if (b < a)
{
for (b = 10; b < 15; ++b)
{
d ();
}
}

P.S. Надеюсь, что тег source сработает )
rsdn.ru/forum/humour/1600906.aspx

Не, ну это же классика кун-фу:

Сначала ты не знаешь, что нельзя делать то-то
Потом знаешь, что нельзя делать то-то
Потом ты понимаешь, что иногда таки можно делать то-то
Ну а потом ты понимаешь, что помимо того-то существует еще шестьдесять шесть способов добиться желаемого, и все из них практически равноправны.
Когда тебя спрашивают «как мне добиться желаемого», ты быстро перебираешь в уме эти шестьдесять шесть способов, прикидываешь то общее, что в них есть, вздыхаешь и говоришь: «вообще-то, главное — гармония.»
На вопрос обиженных учеников: «а как ее добиться?», ты говоришь: «никогда не делайте то-то».
Класс!
А вообще-то, машину состояний можно реализовать ещё и на указателях на функции.
Автоматические методы ее реализуют кипой swtich… case, насколько я помню. Хотя идея с указателями на функции гораздо лучше.
А лучше — какие-нибудь функторы и прочие радости С++, т. к. указатели на функции предполагают наличие внешних переменных.
А лучше — написать простой код на том же С++ и не создавать всякие автоматизированно созданные программы!
Надо к оператору break добавить параметр — из скольки циклов выходить (по-умолчанию из одного). Вот была бы сказка.

А если по-существу, то из скольки угодно вложенных циклов можно выйти с помощью return.
Надо просто правильно выделить свои циклы в отдельную функцию.
Короче, лично для меня законность использования goto не доказана
В php у break и continue есть такой параметр.
В проекте размером в 1500 файлов на C# встретилось 99 goto. Делятся на три класса:
— выход из нескольких циклов
— недоделанные циклы «пока пользователь не введет правильные данные». Его можно было бы заменить на цикл for, а goto — на continue, но тогда нарушается другой принцип: переменные приходится описывать не в момент их инициализации, а раньше, за пределами for. А это еще менее красиво, чем goto.
— странная конструкция такого вида:
[code]
switch(x){
case 1: A(); goto _cont;
case 2: B(); goto _cont;
case 3: C(); goto _cont;
case 4:
D();
_cont:
большой код, который выделять в отдельный метод не хочется.
}
[/code]
Если бы вместо switch была цепочка if-elseif, можно было бы написать
[code]
for(;;){
if(x==1) A();
else if(x==2) B();
else if(x==3) C();
else if(x==4) D();
else break;
большой код
break;
}
[/code]
но как переделать switch без потери эффективности, я не придумал.
Я бы в данном случае сделал бы массив указателей на функции и switch заменил на один-два if. Ну вот типа того:
typedef /*указатель на функцию*/ FunctPtr;
FunctPtr sw[] = {A, B, C, D};
if (x < 4) sw[x - 1] ();

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

if(x<4){
  switch(x){...}
  большой код
}
Другой вариант — флаг использовать, ... case 4: D(); flag = true; ... if (flag) { большой код }
Возможно я не совсем прав, но разве после перехода по goto у нас вызовутся необходимые деструкторы? В сложных программах память будет утекать очень быстро
Да, все необходимые деструкторы вызываются. А вот конструкторы — нет, и goto в обход описания переменной компиляторы запрещают.
Для тех, кто следит за темой — я в конце статьи вставил плюсы и минусы использования goto. Если кто хочет добавить/исправить — пишите в ответы сюда
Тема goto породила новые блоги и посты — приятно :-)! И в одном из них я прочитал замечательный комментарий от runnig:
Мы как-то с друзьями спорили насчёт goto.
Решили посмотреть на самые популярные, проверенные временем программы, которыми пользуются миллионы разработчиков: исходники ядра линукс, llvm, gtk, gstreamer, android и т.д.
В каждой из этих программ используется goto.

Эта мысль, кстати, проскакивала во многих комментариях к статье — но противники goto ее в упор не замечают.
Кто-то тут правильно сказал: «goto нельзя использовать — это правило. Не забываем, что из каждого правила есть исключения». Об этом — о необходимости исключений — говорит теорема Гёделя. Мне кажется, это самое корректное отношение к goto!
Я не считаю goto злом. Никакая языковая конструкция сама по себе не является ни злом, ни добром. Это инструмент. Хочешь использовать — используй правильно. Не хочешь — не используй вообще. Что касается практики: я пишу на С++, и в моих программах goto нет вообще. Он мне не нужен.
Только что пришлось написать вот такой код:

        public int[,] ReadByCoil(out int npts) {
            int[,] res=null;
            npts=0;
            if(m_curArea!=0) goto _1;
_2:
            if(!StartNewSect()) return null;
_1:
            res=ReadCoil();
            if(res==null) goto _2;
            npts=m_npt;
            return res;
        }

Он мне не нравится, и без goto хотелось бы обойтись. Но как это сделать, не дублируя строчек исполняемого кода, я совершенно не понимаю :(
Не раскрыта тема goto c неопределенным поведением как здесь например:
goto Label;

for (;;)
{
int p = 0;
Label:
p += 10;
}

Вы что то заикнулись, но ничего не сказали что говорит об этом стандарт.

А что будет здесь:

void f()
{
int i = 10;
int p = 1;
Label:
p = i;
}

main()
{
f();
goto Label;
}

Что будет?
А если вообще метка будет стоять в методе класса, ни одного объекта которого еще не инстанцировано?
А если инстанцирован?

Интересуют ограничения использования goto по стандарту.
интересует строгое изложение фактов, можно со ссылками на авторитетные источники, авторитетных спецов типа Майерса, Страуструпа и т.д.
А то что goto может быть полезен и сократить код и улучшить читаемость в этом нет никаких сомнений.
Пост был написан 7 лет назад. Интерсно автор с тех пор пересмотрел своё отношение к goto?
В своё время пришлось писать на языке в котором отсутствует goto в любом его проявлении, с тех пор прекрасно обхожусь без него в C/C++.

Кстати, первую блоксхему можно давать кандидатам при приёме на работу. ))
Эх, жаль, опоздал к такому интересному холивару… Ну да может кто-нибудь еще тут что-то читает…

Проблема статьи в том, что автор, как мне кажется, неверно понимает причины, по которым был запрещен оператор перехода в высокоуровневых языках. А запрещен он по простой и банальной причине:

Структуру кода, содержащего этот оператор, компилятор перестает понимать.

Дело не в том, что новичку трудно прочитать код с goto. Мой опыт разработки 15 лет, и я второй, более длинный листинг с while читаю гораздо легче, чем первый, более короткий, но с goto. Потому что я мысленно провожу с кодом ту же операцию, с которой начинает обработку кода компилятор. Я строю абстрактное синтаксическое дерево. AST. А наличие оператора перехода связывает ветви этого дерева вместе, превращая его из дерева в многосвязный граф. А работа с такими графами куда менее удобна, как человеку, так и машине, по ряду причин. Укажу на основную.

Такой код невозможно разделить на макроблоки. Ни явно, ни мысленно. Поотму что необходимым условием существования макроблока является его изолированность. Если не в смысле используемых данных (а хорошо бы), то хотя бы в смысле code flow.

Проблема автора, как мне кажется, состоит в том, что он читает (или читал, много же времени прошло) программы строчка за строчкой, не пытаясь сперва поделить их на более крупные блоки, не вчитываясь. Так можно изучать код, когда тебе 19 и мозг твой свеж, а размер всей программы не превышает 1000 строк.

История знает много практик, которые прежде были или вынужденной мерой, или просто считались нормой, а потом перестали быть таковыми:

— Как вы, например, отнесетесь к программисту, который задекларирует все переменные в глобальной области, а потом будет использовать их в разных местах программы, переиспользуя по нескольку раз?

— А как вы будете смотреть на того, кто напишет всю программу в одной единственной функции?

— А что вы будете думать о том, кто везде, где не попадя понаставит void*?

— А как отнесетесь к тому, кто по поводу и без везде понаставит asm-вставки, потому что решит, что он умнее компилятора?

Все эти практики объединяет с практикой использования goto один набор общих свойств:

1. Код, написанный на их основе хуже понятен компилятору и, как следствие, труднее оптимизируем. Компилятор, например, может развернуть цикл, если видит цикл. Если он видит 10:… goto 10, то он не поймет, что программист имел в виду. Ну… или компилятор точно гораздо умнее программиста.

2. Код, написанный на их основе труднее делить на части и блоки (кроме void*, там проблема другого порядка). Из-за этого он хуже поддерживается

3. И, наконец, такой код существенно сложнее отлаживать. Как минимум, потому, что не вчитываясь в каждую строку (и при этом не изучая всю программу целиком), совершенно невозможно нормально локализовать ошибки.

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

Пример: www.dechifro.org/dcraw/dcraw.c
Встречайте детище великого и ужасного Дейва Коффина — программу обработки «сырых» фотоснимков dcraw. Он написал и поддерживает ее один. Когда-то ее, по слухам, пытались понять разработчики из Adobe, и, кажется, решили, что написать с нуля проще.

Лет десять понадобилось сообществу libraw (https://github.com/LibRaw/LibRaw), чтобы сделать из нее библиотеку. Они просто разобрали логику. Всего лишь распутали программу и разделили ее на части. А теперь понемножку патчат и следят за обновлениями от автора.

Оператор goto в программе dcraw встречается 31 раз среди 23663 строк кода, который, к тому же, весь содержится в единственном файле (видимо, чтобы читать было удобнее). Мне кажется, что каждого человека, который защищает использование goto в современном промышленном программировании, необходимо отправлять читать этот исходник. Может, что-то новое он для себя при этом усвоит.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории