Я действительно стесняюсь спросить об этом, потому что я не хочу «требовать дебатов, аргументов, опросов или расширенных дискуссий», но я новичок в C и хочу получить больше понимания общих шаблонов, используемых в языке.
Недавно я услышал некоторое отвращение к goto
команде, но я также недавно нашел достойный вариант использования для нее.
Код как это:
error = function_that_could_fail_1();
if (!error) {
error = function_that_could_fail_2();
if (!error) {
error = function_that_could_fail_3();
...to the n-th tab level!
} else {
// deal with error, clean up, and return error code
}
} else {
// deal with error, clean up, and return error code
}
Если часть очистки очень похожа, можно было бы написать немного красивее (мое мнение?) Примерно так:
error = function_that_could_fail_1();
if(error) {
goto cleanup;
}
error = function_that_could_fail_2();
if(error) {
goto cleanup;
}
error = function_that_could_fail_3();
if(error) {
goto cleanup;
}
...
cleanup:
// deal with error if it exists, clean up
// return error code
Это распространенный или приемлемый вариант использования goto
C? Есть ли другой / лучший способ сделать это?
c
design-patterns
goto
Robz
источник
источник
goto hell;
Ответы:
goto
Утверждение (и его соответствующие метки) являются управлением потоком примитива (наряду с условным исполнением заявления). Под этим я подразумеваю, что они существуют для того, чтобы вы могли создавать сети управления потоком программ. Вы можете думать о них как о моделировании стрелок между узлами потоковой диаграммы.Некоторые из них могут быть оптимизированы немедленно, когда есть прямой линейный поток (вы просто используете последовательность основных операторов). Другие шаблоны лучше всего заменять структурами структурированного программирования, где они доступны; если это похоже на
while
цикл, используйтеwhile
цикл , хорошо? Шаблоны структурированного программирования определенно, по крайней мере, потенциально более понятны, чем путаницаgoto
утверждений.Тем не менее C не включает в себя все возможные конструкции структурированного программирования. (Мне не ясно, все ли соответствующие из них были обнаружены; скорость обнаружения сейчас медленная, но я не решусь сразу сказать, что все они были найдены.) Из тех, о которых мы знаем, С определенно не хватает
try
/catch
/finally
структура (и исключения тоже). В нем также отсутствует многоуровневыйbreak
цикл. Это те вещи, которыеgoto
могут быть использованы для реализации. Можно использовать и другие схемы, чтобы сделать это тоже - мы знаем, что C имеет достаточный наборgoto
примитивы - но они часто включают создание флаговых переменных и гораздо более сложные условия цикла или защиты; увеличение степени запутанности контрольного анализа с анализом данных усложняет понимание программы в целом. Это также затрудняет оптимизацию компилятора и быстрое выполнение ЦП (большинство конструкций управления потоками - и, безусловно,goto
- очень дешевые).Таким образом, хотя вы не должны использовать без
goto
необходимости, вы должны знать, что он существует и что он может быть необходим, и если вам это нужно, вы не должны чувствовать себя слишком плохо. Примером случая, когда это необходимо, является освобождение ресурса, когда вызываемая функция возвращает условие ошибки. (То естьtry
/finally
.) Можно написать, что без этого,goto
но без этого, могут быть свои недостатки, такие как проблемы его обслуживания. Пример кейса:Код может быть еще короче, но этого достаточно, чтобы продемонстрировать это.
источник
try/catch/finally
чтобыgoto
, несмотря на подобных, но более распространенных (как она может распространяться по нескольким функциям / модулей) форм запутанного кода , который возможен с помощьюtry/catch/finally
?Да.
Он используется, например, в ядре Linux. Вот электронное письмо от конца ветки почти десятилетия назад , написанное жирным шрифтом:
Тем не менее, требуется много дисциплины, чтобы не создавать спагетти-код, как только вы привыкнете к использованию goto, поэтому, если вы не пишете что-то, что требует скорости и небольшого объема памяти (например, ядра или встроенной системы), вы должны действительно Подумайте об этом, прежде чем написать первый goto.
источник
По моему мнению, код, который вы разместили, является примером правильного использования
goto
, потому что вы только перепрыгиваете вниз и используете его только как примитивный обработчик исключений.Однако из-за старых дебатов о goto программисты избегали
goto
около 40 лет, и поэтому они не привыкли читать код с помощью goto. Это веская причина, чтобы избежать перехода: это просто не стандарт.Я бы переписал код как нечто более простое для чтения программистами C:
Преимущества этого дизайна:
источник
Известной статьей, описывающей случай правильного использования, было Структурное программирование с GOTO Заявление Дональда Кнута (Стэнфордский университет). Статья появилась в те дни, когда использование GOTO считалось грехом для некоторых, и когда движение за структурированное программирование было на пике. Возможно, вы захотите взглянуть на GoTo считается вредным.
источник
В Java вы бы сделали это так:
Я использую это много. Как бы мне это не
goto
нравилось, в большинстве других языков C-стиля я использую ваш код; нет другого хорошего способа сделать это. (Прыжок из вложенных циклов - это аналогичный случай; в Java я использую метку,break
а везде я используюgoto
.)источник
Я думаю , что это достойный случай использования, но в случае «ошибка» не что иное , как логическое значение, есть другой способ сделать то , что вы хотите:
Это использует оценку короткого замыкания логических операторов. Если это «лучше», зависит от вашего личного вкуса и от того, как вы привыкли к этой идиоме.
источник
error
значение может стать бессмысленным со всеми операциями ИЛИ.Руководство по стилю linux дает конкретные причины для использования
goto
, которые соответствуют вашему примеру:https://www.kernel.org/doc/Documentation/process/coding-style.rst
Отказ от ответственности Я не должен делиться своей работой. Приведенные здесь примеры немного надуманы, поэтому, пожалуйста, потерпите меня.
Это хорошо для управления памятью. Недавно я работал над кодом, в котором динамически выделялась память (например,
char *
возвращаемая функцией). Функция, которая просматривает путь и выясняет, является ли путь допустимым, анализируя токены пути:Теперь для меня следующий код намного приятнее и его легче поддерживать, если вам нужно добавить
varNplus1
:Теперь в коде были и другие проблемы, а именно то, что N было где-то выше 10, а функция была более 450 строк с 10 уровнями вложенности в некоторых местах.
Но я предложил своему супервайзеру провести его рефакторинг, что я и сделал, и теперь это набор функций, которые все короткие, и все они имеют стиль linux
Если мы рассмотрим эквивалент без
goto
s:Для меня, в первом случае, для меня очевидно, что если первая функция вернется
NULL
, мы уйдем отсюда и вернемся0
. Во втором случае мне нужно прокрутить вниз, чтобы увидеть, что if содержит всю функцию. Конечно, первый указывает на это для меня стилистически (название «out
»), а второй - на синтаксический. Первый еще более очевиден.Кроме того, я предпочитаю иметь
free()
операторы в конце функции. Это отчасти потому, что, по моему опыту,free()
операторы в середине функций плохо пахнут и указывают мне, что я должен создать подпрограмму. В этом случае я создалvar1
свою функцию и не смогfree()
ее выполнить в подпрограмме, но поэтомуgoto out_free
стиль goto out настолько практичен.Я думаю, что программистов нужно воспитывать, полагая, что
goto
это зло. Затем, когда они станут достаточно зрелыми, им следует просмотреть исходный код Linux и прочитать руководство по стилю linux.Я должен добавить, что я использую этот стиль очень последовательно, каждая функция имеет int
retval
,out_free
label и out label. Благодаря стилистической последовательности улучшается читаемость.Бонус: ломается и продолжается
Скажем, у вас есть время цикла
Есть другие вещи не так с этим кодом, но одна вещь, это оператор continue. Я хотел бы переписать все это, но мне было поручено немного изменить его. Мне потребовалось бы несколько дней, чтобы провести рефакторинг таким образом, чтобы это меня удовлетворило, но фактическое изменение заняло около половины дня. Проблема в том, что даже если мы «
continue
», нам все равно нужно освободитьvar1
иvar2
. Я должен был добавитьvar3
, и это заставило меня хотеть рвать, чтобы отражать выражения free ().Я был относительно новым стажером в то время, но некоторое время назад я искал исходный код linux для развлечения, поэтому я спросил своего руководителя, могу ли я использовать оператор goto. Он сказал да, и я сделал это:
Я думаю, что продолжение в порядке в лучшем случае, но для меня это как goto с невидимым лейблом. То же самое касается перерывов. Я бы все же предпочел продолжить или прервать, если, как это было в данном случае, это не заставит вас отражать изменения в нескольких местах.
И я должен также добавить, что это использование
goto next;
иnext:
этикетка являются неудовлетворительными для меня. Они просто лучше, чем зеркальное отражениеfree()
иcount++
утверждения.goto
Почти всегда они ошибаются, но нужно знать, когда они полезны.Одна вещь, которую я не обсуждал, это обработка ошибок, которая была покрыта другими ответами.
Представление
Можно посмотреть на реализацию strtok () http://opensource.apple.com//source/Libc/Libc-167/string.subproj/strtok.c
Пожалуйста, исправьте меня, если я ошибаюсь, но я верю, что
cont:
метка иgoto cont;
утверждение предназначены для повышения производительности (они, конечно, не делают код более читабельным). Их можно заменить читабельным кодом, выполнивпропустить разделители. Но чтобы быть максимально быстрым и избежать ненужных вызовов функций, они делают это таким образом.
Я прочитал статью Дейкстры и нахожу ее довольно эзотерической.
Google "Dijkstra Goto заявление считается вредным", потому что у меня недостаточно репутации, чтобы разместить более 2 ссылок.
Я видел, что это цитируется как причина не использовать goto, и чтение ничего не изменило, если принять во внимание, что я использую goto.
Приложение :
Я придумал аккуратное правило, думая обо всем этом о продолжениях и перерывах.
Это не всегда возможно из-за проблем с областью видимости, но я обнаружил, что это значительно упрощает анализ моего кода. Я заметил, что всякий раз, когда в цикле while происходит разрыв или продолжение, это вызывает у меня плохое предчувствие.
источник
Лично я бы рефакторинг это больше так:
Это было бы более мотивированным, избегая глубокого вложения, чем избегая перехода, однако (IMO - более серьезная проблема с первым образцом кода), и, конечно, зависело бы от возможного выхода CleanupAfterError из области видимости (в этом случае «params» мог бы быть структурой, содержащей некоторую выделенную память, которую вам нужно освободить, ФАЙЛ *, который вам нужно закрыть или что-то еще).
Одно из основных преимуществ этого подхода заключается в том, что проще и чище выделить гипотетический будущий дополнительный шаг, скажем, между FTCF2 и FTCF3 (или удалить существующий текущий шаг), так что он лучше поддается обслуживанию (и человеку, который наследует мой код, не желая линчевать меня!) - в сторону, во вложенной версии этого нет.
источник
Ознакомьтесь с рекомендациями по кодированию на языке C MISRA (Ассоциация по надежности программного обеспечения автомобильной промышленности), которые позволяют переходить по строгим критериям (что соответствует вашему примеру).
Там, где я работаю, будет написан один и тот же код - без необходимости - избегать ненужных религиозных дискуссий о них - большой плюс в любой фирме программного обеспечения.
или для "goto in drag" - что-то еще более хитрое, чем goto, но обходится без "Goto Ever !!!" лагерь) "Конечно, все должно быть в порядке, не использовать Гото" ....
Если функции имеют одинаковый тип параметра, поместите их в таблицу и используйте цикл -
источник
if(flag)
в одной копии принимает ветвь «если», а каждая соответствующая инструкция в другой копии принимает « еще». Действия, которые устанавливают и сбрасывают флаг, на самом деле являются «gotos», которые перемещаются между этими двумя версиями кода. Бывают случаи, когда использование флагов чище, чем любая альтернатива, но добавление флага для сохранения однойgoto
цели не является хорошим компромиссом.Я также использую,
goto
если альтернативныйdo/while/continue/break
хакер будет менее читабельным.goto
Преимущество в том, что у их целей есть имя, и они читаютgoto something;
. Это может быть более удобным для чтения , чемbreak
или ,continue
если вы на самом деле не остановить или продолжить что - то.источник
do ... while(0)
или другой конструкции, которая не является реальным циклом, а является попыткой предотвратить использование agoto
.источник
break
работает точно так жеgoto
, хотя и не несет стигмы.Всегда будут лагеря, которые говорят, что один путь приемлем, а другой нет. Компании, на которые я работал, нахмурились или категорически не одобряли использование GoTo. Лично я не могу думать ни о каком времени, когда использовал один, но это не значит, что они плохие , просто еще один способ сделать что-то.
В Си я обычно делаю следующее:
Для обработки, используя ваш пример goto, я бы сделал это:
error = function_that_could_fail_1 (); if (! error) {error = function_that_could_fail_2 (); } if (! error) {error = function_that_could_fail_3 (); }
Здесь нет вложенности, и внутри предложений if вы можете делать любые сообщения об ошибках, если на шаге возникла ошибка. Таким образом, это не должно быть «хуже», чем метод с использованием gotos.
Я еще не сталкивался со случаем, когда у кого-то есть gotos, который нельзя сделать с помощью другого метода и который так же читабелен / понятен, и это ключ, ИМХО.
источник