Це гідний приклад використання для goto in C?


59

Я дуже вагаюся з цим питанням, тому що я не хочу "вимагати дискусій, аргументів, опитувань чи розширених дискусій", але я новачок у С і хочу отримати більше розуміння загальних моделей, що використовуються в мові.

Нещодавно я почув деяку неприємність до 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у С? Чи є інший / кращий спосіб зробити це?



2
Більш ніж цукрове покриття, компілятор гарантує очищення та перевірку помилок, тоді як у C ви повинні робити це вручну, порушуючи DRY і гарантуючи, що в якомусь місці ви лінуєтеся перевірити код помилки або перейшли на неправильну етикетку чи щось подібне.
DeadMG

3
@Izkata: Ні, сенс полягає у функціях (як деструктори), які неявно викликаються компілятором, коли викидається виняток.
DeadMG

6
@DeadMG: Використання іншої мови - це не завжди варіант.
Бенджамін Клостер

13
Я думаю, що при відповідних назвах етикеток приклад може виглядати цілком пристойно:goto hell;
gnat

Відповіді:


48

Оператор goto(і відповідні його мітки) є примітивом управління потоком (поряд із умовним виконанням оператора). Маючи на увазі, я маю на увазі, що вони є там, щоб ви могли побудувати мережі управління потоками програм. Ви можете вважати їх моделюванням стрілок між вузлами блок-схеми.

Деякі з них можна оптимізувати негайно, де є прямий лінійний потік (ви просто використовуєте послідовність основних операторів). Інші зразки найкраще замінити структурованими структурами програмування там, де вони є; якщо це схоже на whileцикл, використовуйте whileцикл , добре? Структуровані схеми програмування, безумовно, принаймні потенційно чіткіші за наміри, ніж безладдя gotoтверджень.

Тим не менш, C не включає всі можливі структуровані програми програмування. (Мені незрозуміло, що все відповідне все ще було виявлено; швидкість виявлення зараз повільна, але я б не вагаючись перейшов, сказавши, що всі знайдені.) З тих, про кого ми знаємо, С точно не вистачає try/ catch/ finallyструктура (і винятки теж). Також йому не вистачає багаторівневої break-від-петлі. Це такі речі, які gotoможна використовувати для реалізації. Для цього можна використовувати й інші схеми - ми знаємо, що C має достатній набірgotoпримітиви - але вони часто включають створення змінних прапорів та набагато складніші умови циклу чи захисту; посилення заплутаності контрольного аналізу з аналізом даних робить програму важче зрозуміти загалом. Також компілятор ускладнює оптимізацію та швидке виконання процесора (більшість конструкцій управління потоком - і, безумовно, goto - дуже дешеві).

Таким чином, хоча ви не повинні використовувати, gotoякщо не потрібно, ви повинні знати, що він існує і що він може знадобитися, і якщо вам це потрібно, ви не повинні почувати себе занадто погано. Прикладом випадку, коли це потрібно, є розподіл ресурсів, коли викликана функція повертає стан помилки. (Тобто, try/ finally.) Можна писати, що, gotoале не робити цього, можуть бути і самі мінуси, такі як проблеми його підтримання. Приклад справи:

int frobnicateTheThings() {
    char *workingBuffer = malloc(...);
    int i;

    for (i=0 ; i<numberOfThings ; i++) {
        if (giveMeThing(i, workingBuffer) != OK)
            goto error;
        if (processThing(workingBuffer) != OK)
            goto error;
        if (dispatchThing(i, workingBuffer) != OK)
            goto error;
    }

    free(workingBuffer);
    return OK;

  error:
    free(workingBuffer);
    return OOPS;
}

Код може бути ще коротшим, але цього достатньо, щоб продемонструвати суть.


4
+1: У C goto технічно ніколи не потрібен - завжди є спосіб це зробити, він стає безладним ..... Для надійного набору вказівок щодо використання goto погляд на MISRA C.
mattnz

1
Ви б віддали перевагу , try/catch/finallyщоб goto, незважаючи на подібну, але більш поширеною (як вона може поширюватися по кількох функцій / модулів) форми коду спагетті , який можливий з допомогою try/catch/finally?
аутист

65

Так.

Він використовується, наприклад, в ядрі Linux. Ось електронний лист із кінця потоку майже десятиліття тому , що засмічує моє:

Від: Роберт Лав
Тема: Re: будь-який шанс тестування 2.6.0 *?
Дата: 12 січня 2003 17:58:06 -0500 В

Нд, 2003-01-12 о 17:22, Роб Вілкенс написав:

Я кажу "будь ласка, не використовуй goto", а замість цього функцію "cleanup_lock" і додаю, що перед усіма поверненнями заяви. Це не повинно бути тягарем. Так, це просить розробника попрацювати трохи важче, але кінцевий результат - кращий код.

Ні, це грубо, і воно розпускає ядро . Він вказує купу небажаної для N шляхів помилок, на відміну від коду виходу один раз в кінці. Слід кеша є ключовим, і ви просто вбили його.

Не легше читати.

Як остаточний аргумент, це не дозволяє нам чисто робити звичайний вітер і розмотати , тобто

        do A
        if (error)
            goto out_a;
        do B
        if (error)
            goto out_b;
        do C
        if (error)
            goto out_c;
        goto out;
        out_c:
        undo C
        out_b:
        undo B:
        out_a:
        undo A
        out:
        return ret;

Тепер припиніть це.

Роберт Лав

Однак це вимагає великої дисципліни, щоб не створювати код спагетті, коли ви звикли використовувати Goto, тому, якщо ви не пишете щось, що вимагає швидкості та низької площі пам’яті (наприклад, ядро ​​чи вбудована система), вам дійсно слід подумайте над цим, перш ніж написати перше гото.


21
Зауважте, що ядро ​​відрізняється від програми, що не має ядра, що стосується пріоритету швидкості в режимі "необроблене" порівняно з читабельністю. Іншими словами, вони ВЖЕ ПРОФІЛЮвались і виявили, що їм потрібно оптимізувати код для швидкості за допомогою goto.

11
Використання безвітрового стеку для обробки помилок без помилок, фактично не натискаючи на стек! Це приголомшливе використання гото.
mike30

1
@ user1249, Сміття, ви не можете профайлювати кожне {минуле, існуюче, майбутнє} додаток, яке використовує фрагмент {бібліотеки, ядра} коду. Вам просто потрібно бути швидким.
Pacerier

1
Непов’язане: Я вражений тим, як люди можуть використовувати списки розсилки, щоб зробити щось, не кажучи вже про такі масштабні проекти. Це просто так ... примітивно. Як люди заходять із каміном повідомлень ?!
Олександр

2
Мене не так хвилює помірність. Якщо хтось досить м'який, щоб його відвернули якийсь мудак в Інтернеті, ваш проект, мабуть, краще без них. Мене більше турбує недоцільність дотримуватися загородження вхідних повідомлень, і як, можливо, ви могли б мати природну зворотну дискусію з таким невеликим інструментом для відстеження цитат, наприклад.
Олександр

14

На мою думку, опублікований вами код є прикладом правильного використання goto, оскільки ви лише стрибаєте вниз і використовуєте його лише як примітивний обробник винятків.

Однак через стару дискусію про гото програмісти уникають gotoвже 40 років, і тому вони не використовуються для читання коду з goto. Це поважна причина уникати goto: це просто не стандарт.

Я б переписав код як щось легше читається програмістами C:

Error some_func (void)
{
  Error error;
  type_t* resource = malloc(...);

  error = some_other_func (resource);

  free (resource);

  /* error handling can be placed here, or it can be returned to the caller */

  return error;
}


Error some_other_func (type_t* resource)  // inline if needed
{
  error = function_that_could_fail_1();
  if(error)
  {
    return error;
  }

  /* ... */

  error = function_that_could_fail_2();
  if(error)
  {
    return error;
  }

  /* ... */

  return ok;
}

Переваги такої конструкції:

  • Функція, яка виконує фактичну роботу, не повинна стосуватися завдань, які не мають відношення до її алгоритму, наприклад, розподілу даних.
  • Код буде виглядати менш чужорідним для програмістів C, оскільки вони бояться goto та ярликів.
  • Ви можете централізувати обробку помилок і розстановку місця в одному місці, поза функцією алгоритму. Функція не має сенсу керувати власними результатами.

11

У відомому документі, що описує випадок дійсного використання, було Структурне програмування із заявою GOTO Дональда Е. Кнута (Стенфордський університет). Документ з'явився в ті часи, коли використання GOTO деякими вважалося гріхом, і коли рух за Структуроване програмування був на піку. Ви можете поглянути на GoTo вважається шкідливим.


9

У Java ви зробите це так:

makeCalls:  {
    error = function_that_could_fail_1();
    if (error) {
        break makeCalls;
    }
    error = function_that_could_fail_2();
    if (error) {
        break makeCalls;
    }
    error = function_that_could_fail_3();
    if (error) {
        break makeCalls;
    }
    ...
    return 0;  // No error code.
}
// deal with error if it exists, clean up
// return error code

Я цим дуже користуюся. Наскільки мені не подобається goto, в більшості інших мов C-стилю я використовую ваш код; немає іншого хорошого способу зробити це. (Вистрибування з вкладених циклів - подібний випадок; у Java я використовую мітку, breakа всюди я використовую goto.)


3
О, це акуратна структура управління.
Брайан Боттчер

4
Це справді цікаво. Я б зазвичай думав використовувати структуру try / catch / нарешті для цього в java (кидаючи винятки, а не ламаючи).
Робз

5
Це справді нечитабельно (принаймні для мене). Якщо є, винятки набагато кращі.
m3th0dman

1
@ m3th0dman Я погоджуюся з вами в цьому конкретному прикладі (обробка помилок). Але є й інші (не виняткові) випадки, коли ця ідіома могла б стати в нагоді.
Конрад Рудольф

1
Винятки дорогі, їм потрібно генерувати помилки, стек-тракцію та багато іншого. Ця перерва мітки дає чистий вихід із циклу перевірки. якщо ніхто не піклується про пам’ять і швидкість, то для всіх я дбаю про виняток.
Цхаллачка

8

Я думаю, що це пристойний випадок використання, але у випадку, якщо "помилка" - це не що інше, як булеве значення, є інший спосіб досягти того, що ви хочете:

error = function_that_could_fail_1();
error = error || function_that_could_fail_2();
error = error || function_that_could_fail_3();
if(error)
{
     // do cleanup
}

Для цього використовується оцінка короткого замикання булевих операторів. Якщо це "краще", залежить від вашого особистого смаку і того, як ви звикли до цієї ідіоми.


1
Проблема в цьому полягає в тому error, що цінність може стати безглуздою при всіх АР'інг.
Джеймс

@James: відредагував мою відповідь через ваш коментар
Doc Brown

1
Цього недостатньо. Якщо під час першої функції сталася помилка, я не хочу виконувати другу або третю функцію.
Робз

2
Якщо під короткою рукою ви маєте на увазі оцінку короткого замикання , це точно не те, що робиться тут через використання побітового АБО замість логічного АБО.
Кріс каже, що знову відбудеться Моніка

1
@ChristianRau: спасибі, відповідно відредагував мою відповідь
Doc Brown

6

Посібник зі стилю linux дає конкретні причини для використання gotos, які відповідають вашому прикладу:

https://www.kernel.org/doc/Documentation/process/coding-style.rst

Обґрунтуванням використання gotos є:

  • безумовні твердження легше зрозуміти і слідувати
  • гніздування скорочується
  • помилки, не оновлюючи окремі точки виходу під час внесення змін, запобігаються
  • зберігає роботу компілятора для оптимізації зайвого коду;)

Заперечення Я не повинен ділитися своєю роботою. Приклади тут трохи надумані, тому нехай будь ласка, нехай з собою.

Це добре для управління пам’яттю. Нещодавно я працював над кодом, який динамічно розподіляв пам'ять (наприклад, char *повернута функцією). Функція, яка розглядає шлях і визначає, чи дійсний шлях, аналізуючи лексеми шляху:

tmp_string = strdup(string);
token = strtok(tmp_string,delim);
while( token != NULL ){
    ...
    some statements, some involving dynamically allocated memory
    ...
    if ( check_this() ){
        free(var1);
        free(var2);
        ...
        free(varN);
        return 1;
    }
    ...
    some more stuff
    ...
    if(something()){
        if ( check_that() ){
            free(var1);
            free(var2);
            ...
            free(varN);
            return 1;
        } else {
            free(var1);
            free(var2);
            ...
            free(varN);
            return 0;
        }
    }
    token = strtok(NULL,delim);
}

free(var1);
free(var2);
...
free(varN);
return 1;

Тепер для мене наступний код набагато приємніший і простіший в обслуговуванні, якщо вам потрібно додати varNplus1:

int retval = 1;
tmp_string = strdup(string);
token = strtok(tmp_string,delim);
while( token != NULL ){
    ...
    some statements, some involving dynamically allocated memory
    ...
    if ( check_this() ){
        retval = 1;
        goto out_free;
    }
    ...
    some more stuff
    ...
    if(something()){
        if ( check_that() ){
            retval = 1;
            goto out_free;
        } else {
            retval = 0;
            goto out_free;
        }
    }
    token = strtok(NULL,delim);
}

out_free:
free(var1);
free(var2);
...
free(varN);
return retval;

Тепер у коду були всілякі інші проблеми з ним, а саме те, що N десь вище 10, а функція - понад 450 рядків, де місцями 10 рівнів вкладеності.

Але я запропонував моєму керівнику переробити його, що я і зробив, і тепер це купа функцій, які всі короткі, і всі вони мають стиль Linux

int function(const char * param)
{
    int retval = 1;
    char * var1 = fcn_that_returns_dynamically_allocated_string(param);
    if( var1 == NULL ){
        retval = 0;
        goto out;
    }

    if( isValid(var1) ){
         retval = some_function(var1);
         goto out_free;
    }

    if( isGood(var1) ){
         retval = 0;
         goto out_free;
    }

out_free:
    free(var1);
out:
    return retval;
}

Якщо розглядати еквівалент без gotos:

int function(const char * param)
{
    int retval = 1;
    char * var1 = fcn_that_returns_dynamically_allocated_string(param);
    if( var1 != NULL ){

       if( isValid(var1) ){
            retval = some_function(var1);
       } else {
          if( isGood(var1) ){
               retval = 0;
          }
       }
       free(var1);

    } else {
       retval = 0;
    }

    return retval;
}

Мені, в першому випадку, для мене очевидно, що якщо повернеться перша функція NULL, ми тут далеко і повертаємось 0. У другому випадку я повинен прокрутити вниз, щоб побачити, що якщо міститься вся функція. Наданий перший визначає це для мене стилістично (назва " out"), а другий робить це синтаксично. Перший все-таки більш очевидний.

Крім того, я дуже вважаю за краще мати free()заяви в кінці функції. Це частково тому, що, з мого досвіду, free()твердження посеред функцій погано пахнуть і вказують мені, що я повинен створити підпрограму. У цьому випадку я створив var1свою функцію і не зміг free()її виконати в підпрограмі, але саме тому goto out_freeстиль goto out настільки практичний.

Я думаю, що програмістів потрібно виховувати, вважаючи, що gotoце зло. Потім, коли вони достатньо зрілі, вони повинні переглядати вихідний код Linux і читати посібник зі стилю Linux.

Додам, що я використовую цей стиль дуже послідовно, у кожній функції є int retval, out_freelabel та out label. Через стилістичну узгодженість покращується читабельність.

Бонус: Перерва та продовження

Скажіть, у вас є певний час

char *var1, *var2;
char line[MAX_LINE_LENGTH];
while( sscanf(line,... ){
    var1 = functionA(line,count);
    var2 = functionB(line,count);

    if( functionC(var1, var2){
         count++
         continue;
    }

    ...
    a bunch of statements
    ...

    count++;
    free(var1);
    free(var2);
}

Є й інші помилки з цим кодом, але одне - це продовження заяви. Я хотів би переписати всю річ, але мені було поставлено завдання змінити її невеликим чином. Минуло б кілька днів, щоб переробити його таким чином, що мене задовольнило, але фактична зміна полягала приблизно в півдня. Проблема полягає в тому, що навіть якщо ми ' continue' нам все-таки потрібно звільнитись var1і var2. Мені довелося додати var3, і це змусило мене захотіти, щоб відобразити вільні () заяви.

У той час я був відносно новим стажером, але я ще раз назад дивився на вихідний код Linux, тому я поцікавився у свого керівника, чи можу я використати goto заяву. Він сказав так, і я це зробив:

char *var1, *var2;
char line[MAX_LINE_LENGTH];
while( sscanf(line,... ){
    var1 = functionA(line,count);
    var2 = functionB(line,count);
    var3 = newFunction(line,count);

    if( functionC(var1, var2){
         goto next;
    }

    ...
    a bunch of statements
    ...
next:
    count++;
    free(var1);
    free(var2);
}

Я думаю, що продовження добре в кращому випадку, але для мене вони схожі на гото з невидимою етикеткою. Те саме стосується перерв. Я б все-таки вважав за краще продовжувати або перервати, якщо, як це було у випадку, це не примушує вас відображати модифікації в декількох місцях.

І я також хочу додати, що таке використання goto next;та next:етикетка для мене незадовільні. Вони просто кращі, ніж дзеркальне відображення free()та count++твердження.

gotoмайже завжди помиляються, але треба знати, коли ними добре користуватися.

Одне, що я не обговорював - це обробка помилок, на яку поширюються інші відповіді.

Продуктивність

Можна подивитися на реалізацію strtok () http://opensource.apple.com//source/Libc/Libc-167/string.subproj/strtok.c

#include <stddef.h>
#include <string.h>

char *
strtok(s, delim)
    register char *s;
    register const char *delim;
{
    register char *spanp;
    register int c, sc;
    char *tok;
    static char *last;


    if (s == NULL && (s = last) == NULL)
        return (NULL);

    /*
     * Skip (span) leading delimiters (s += strspn(s, delim), sort of).
     */
cont:
    c = *s++;
    for (spanp = (char *)delim; (sc = *spanp++) != 0;) {
        if (c == sc)
            goto cont;
    }

    if (c == 0) {       /* no non-delimiter characters */
        last = NULL;
        return (NULL);
    }
    tok = s - 1;

    /*
     * Scan token (scan for delimiters: s += strcspn(s, delim), sort of).
     * Note that delim must have one NUL; we stop if we see that, too.
     */
    for (;;) {
        c = *s++;
        spanp = (char *)delim;
        do {
            if ((sc = *spanp++) == c) {
                if (c == 0)
                    s = NULL;
                else
                    s[-1] = 0;
                last = s;
                return (tok);
            }
        } while (sc != 0);
    }
    /* NOTREACHED */
}

Будь ласка, виправте мене, якщо я помиляюся, але я вважаю, що cont:мітка та goto cont;висловлювання є ефективними (вони, безумовно, не роблять код читабельнішим). Їх можна замінити на читабельний код, виконавши

while( isDelim(*s++,delim));

пропустити роздільники. Але щоб бути максимально швидким і уникати зайвих дзвінків функцій, вони роблять це так.

Я читаю статтю Дайкстра і вважаю це досить езотеричним.

google "dijkstra goto заява вважається шкідливою", оскільки мені не вистачає репутації, щоб розмістити більше 2 посилань.

Я бачив, що це наводиться як причина, щоб не використовувати goto's, і читання цього не змінило нічого, наскільки моє використання goto's.

Додаток :

Я придумав акуратне правило, думаючи про все це про продовження та перерви.

  • Якщо через цикл у вас є продовження, то тіло циклу while повинно бути функцією, а продовження - оператором повернення.
  • Якщо в циклі часу у вас є оператор перерви, то сам цикл while повинен бути функцією, а перерва повинна стати оператором return.
  • Якщо у вас обоє, то щось може бути не так.

Це не завжди можливо через проблеми сфери, але я виявив, що це робить набагато простіше міркувати про мій код. Я помічав, що кожного разу, коли цикл перервався або продовжився, це викликало у мене погане відчуття.


2
+1, але чи можу я погодитися з одного питання? "Я думаю, що програмістів потрібно виховувати, вважаючи, що гото - це зло". Можливо, але я вперше навчився програмувати в BASIC, з номерами рядків та GOTO, без редактора тексту, у 1975 році. Я познайомився зі структурованим програмуванням через десять років, після чого знадобився один місяць, щоб я самостійно перестав використовувати GOTO, без цього будь-який тиск для зупинки. Сьогодні я використовую GOTO кілька разів на рік з різних причин, але це не дуже часто. Якщо я не виховувався в думці, що GOTO - це зло, він не зробив мені ніякої шкоди, яку я знаю, і це навіть могло б принести користь. Це тільки я.
вт

1
Я думаю, ти маєш рацію з цього приводу. Мене виховує думка, що GOTO не повинні використовуватися, і за чистим випадком випадків я переглядав вихідний код Linux у той час, коли я працював над кодом, у якого ці функції з кількома точками виходу з пам'яттю вільними. Інакше я б ніколи не знав про ці методи.
Філіп Карфін

1
@thb Крім того, смішна історія, я запитав свого керівника в той час, як стажиста, про дозвіл на використання GOTO, і я переконався, що я пояснив йому, що збираюся використовувати їх конкретним чином, як спосіб, який він використовується в Ядро Linux, і він сказав: "Добре, це має сенс, а також я не знав, що ви можете використовувати GOTO в C".
Філіп Карфін

1
@thb Я не знаю, чи добре переходити до циклів (замість розбивання циклів), як цей ? Добре, це поганий приклад, але я вважаю, що швидкість з твердженнями goto (приклад 7а) щодо Структурного програмування Кнута з переходом до висловлювань не дуже зрозуміла.
Yai0Phah

@ Yai0Phah Я поясню свою думку, але моє пояснення не зменшує ваш прекрасний приклад 7a! Я схвалюю приклад. Тим не менш, нахабні другокурсники люблять читати лекції людям про гото. Важко знайти практичне використання goto з 1985 року, що викликає значні проблеми, тоді як можна знайти нешкідливі готи, які полегшують роботу програміста. У будь-якому випадку так рідко виникає в сучасному програмуванні, що, коли воно виникає, моя порада: якщо ви хочете ним скористатися, то, мабуть, вам варто просто скористатися ним. Гото добре. Основна проблема goto полягає в тому, що деякі вважають, що знецінення goto змушує їх виглядати розумними.
пт

5

Особисто я б рефактор це більше, як це:

int DoLotsOfStuffThatCouldFail (paramstruct *params)
{
    int errcode = EC_NOERROR;

    if ((errcode = FunctionThatCouldFail1 (params)) != EC_NOERROR) return errcode;
    if ((errcode = FunctionThatCouldFail2 (params)) != EC_NOERROR) return errcode;
    if ((errcode = FunctionThatCouldFail3 (params)) != EC_NOERROR) return errcode;
    if ((errcode = FunctionThatCouldFail4 (params)) != EC_NOERROR) return errcode;

    return EC_NOERROR;
}

void DoStuff (paramstruct *params)
{
    int errcode = EC_NOERROR;

    InitStuffThatMayNeedToBeCleaned (params);

    if ((errcode = DoLotsOfStuffThatCouldFail (params)) != EC_NOERROR)
    {
         CleanupAfterError (params, errcode);
    }
}

Це може бути більш мотивованим уникненням глибокого гніздування, ніж униканням goto, однак (IMO - гірша проблема з першим зразком коду), і, звичайно, буде залежати від того, як CleanupAfterError можливий поза сферою дії (у цьому випадку "парами" могли б бути структурою, що містить деяку виділену пам'ять, яку вам потрібно звільнити, ФАЙЛ *, який вам потрібно закрити чи будь-що інше).

Однією з головних переваг, які я бачу при такому підході, є те, що простіше прокласти гіпотетичний майбутній додатковий крок між, скажімо, FTCF2 та FTCF3 (або видалити існуючий поточний крок) і простіше, і таким чином, він краще піддається ремонту (та людині, яка успадковує мій код, не бажаючи мене лінчувати!) - відійдемо, вкладеної версії цього не вистачає.


1
Я не заявляв цього у своєму питанні, але можливо, що у FTCF НЕ є однакові параметри, що робить цю схему трохи складнішою. Спасибі, хоча.
Робз

3

Погляньте на керівні принципи кодування програм CIS MISRA (Асоціація надійності програмного забезпечення автомобільної промисловості), які дозволяють перейти до жорстких критеріїв (яким відповідає ваш приклад)

Там, де я працюю, був би написаний той самий код - не потрібно готуватися. Уникнення непотрібних релігійних суперечок про них - великий плюс у будь-якому програмному забезпеченні.

error = function_that_could_fail_1();
if(!error) {
  error = function_that_could_fail_2();
}
if(!error) {
  error = function_that_could_fail_3();
} 
if(!error) {
...
if (error) {
  cleanup:
} 

або для "goto in drag" - щось навіть більш хитке, ніж goto, але обходить "No goto Ever !!!" табір) "Безумовно, це повинно бути нормально, не використовує Goto" ....

do {
  if (error = function_that_could_fail_1() ){
    break 
  }
  if (error = function_that_could_fail_2() ){
    break 
  }
  ....... 
} while (0) 
cleanup();
.... 

Якщо функції мають однаковий тип параметрів, покладіть їх у таблицю та використовуйте цикл -


2
Чинні вказівки MISRA-C: 2004 не дозволяють переходити в будь-якій формі (див. Правило 14.4). Зауважте, комітет MISRA завжди з цим плутався, вони не знають, на якій нозі стояти. По-перше, вони беззастережно заборонили використовувати goto, продовжувати і т. Д. Але в проекті майбутнього MISRA 2011 вони хочуть дозволити їх знову. В якості бічної сторони зауважте, що MISRA забороняє присвоювати всередині if-заяви з дуже поважних причин, оскільки це набагато небезпечніше будь-якого використання goto.

1
З аналітичної точки зору додавання прапора до програми еквівалентно дублюванню всього кодового коду, де прапор знаходиться в області дії, кожен з яких if(flag)в одному екземплярі бере гілку "якщо", а кожен відповідний вислів в іншій копії приймає " інше ". Дії, які встановлюють та очищають прапор, насправді є "готосами", які переходять між цими двома версіями коду. Бувають випадки, коли використання прапорів чистіше, ніж будь-яка альтернатива, але додавання прапора для збереження однієї gotoцілі не є корисним компромісом.
supercat

1

Я також використовую, gotoякщо альтернативне do/while/continue/breakхакерство було б менш читабельним.

gotos мають перевагу в тому, що цілі мають ім'я, і ​​вони читають goto something;. Це може бути читабельніше, ніж breakчи continueякщо ви насправді щось не зупиняєте чи продовжуєте.


4
Будь-де всередині do ... while(0)або іншої конструкції, яка не є фактичним циклом, а спробою запобігти використанню goto.
аїб

1
Ах, дякую, я не знав саме цієї марки "Чому б хтось це робив ?!" конструкцій поки.
Бенджамін Клостер

2
Зазвичай хакерство "робити / поки / продовжувати" / "ламати" стає нечитабельним лише тоді, коли модуль, що містить його, в першу чергу занадто довгий.
Джон Р. Стром

2
Я нічого не можу знайти в цьому як виправдання для використання goto. Перерва та продовження мають очевидний наслідок. гото ... куди? Де етикетка? Перерва та переїзд точно розповість вам, де наступний крок та його сусідній пункт.
Rig

1
Звичайно, етикетка повинна бути видно з циклу. Я погоджуюсь із частиною коментаря @John R. Strohm із довжиною котрий довжиною. І ваш пункт, перекладений на хакерство циклу, стає "Вирватися з чого? Це не петля!". У будь-якому випадку, це стає тим, чого ОП побоювалась, що може, тому я відмовляюся від дискусії.
аїб

-1
for (int y=0; y<height; ++y) {
    for (int x=0; x<width; ++x) {
        if (find(x, y)) goto found;
    }
}
found:

Якщо є лише один цикл, він breakпрацює точно так goto, хоча і не має стигми.
9000

6
-1: По-перше, x і y - поза межею знайденого :, тому це вам не допоможе. По-друге, з написаним кодом той факт, що ви приїхали, знайшов: не означає, що ви знайшли те, що шукали.
Джон Р. Стром

Це тому, що це найменший приклад, який я міг би придумати для випадку виривання декількох петель. Будь ласка, відредагуйте його для кращої мітки або готового чека.
аїб

1
Але також пам’ятайте, що функції C необов’язково не мають побічних ефектів.
аїб

1
@ JohnR.Strohm Це не має сенсу ... Етикетка "знайдений" використовується для розбиття циклу, а не для перевірки змінних. Якби я хотів перевірити змінні, я міг би зробити щось подібне: for (int y = 0; y <висота; ++ y) {for (int x = 0; x <width; ++ x) {if (find ( x, y)) {doSomeThingWith (x, y); goto знайдено; }}} знайдено:
YoYoYonnY

-1

Завжди будуть табори, які кажуть, що один спосіб прийнятний, а інший - ні. Компанії, над якими я працював, нахмурилися або сильно не відсторонялися від використання. Особисто я не можу придумати жодного разу, коли я його використав, але це не означає, що вони погані , просто інший спосіб робити речі.

Як правило, на мові C я виконую наступні дії:

  • Перевірте умови, які можуть запобігти обробці (погані входи тощо) та "повернути"
  • Виконайте всі кроки, які потребують розподілу ресурсів (наприклад, mallocs)
  • Виконайте обробку, де кілька кроків перевіряють на успіх
  • Вивільнити будь-які ресурси, якщо вони успішно розподілені
  • Повернути будь-які результати

Для обробки, використовуючи ваш goto-приклад, я б це зробив:

помилка = function_that_could_fail_1 (); if (! помилка) {error = function_that_could_fail_2 (); } if (! помилка) {error = function_that_could_fail_3 (); }

Тут немає вкладення, і всередині пунктів if ви можете робити будь-які повідомлення про помилки, якщо крок генерував помилку. Отже, це не повинно бути «гірше», ніж метод, що використовує gotos.

Мені ще не доводилося стикатися з випадком, коли хтось має готи, які неможливо зробити іншим методом, і це так само читабельно / зрозуміло, і це головне, IMHO.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.