C: вложенные Ifs или Gotos

каков наилучший способ управления ресурсами для C. Должен ли я использовать вложенную структуру if или использовать операторы goto?

Я знаю, что есть много табу о перейти заявления. Тем не менее, я думаю, что это оправдано для очистки местных ресурсов. Я предоставил два образца. Один сравнивает вложенную структуру if, а другой использует операторы goto. Я лично считаю, что заявления goto облегчают чтение кода. Для те, кто может утверждать, что вложенные if подскажите лучшую структуру, представьте, что тип данных был чем-то другим, чем char*, например дескриптором Windows. Я чувствую, что вложенные if структура выйдет из-под контроля с серией CreateFile функции или любая другая функция которая принимает большое количество параметров.

этой статьи демонстрирует, что локальные операторы goto создают RAII для кода C. Код аккуратный легко следовать. Представьте себе, что это серия вложенные if заявления.

Я понимаю, что перейти табу на многих других языках, потому что их существуют другие механизмы управления, такие как try/catch и т. д., Однако в C это кажется уместным.

#include <stdlib.h>

#define STRING_MAX 10

void gotoExample()
{
    char *string1, *string2, *string3, *string4, *string5;

    if ( !(string1 = (char*) calloc(STRING_MAX, sizeof(char))) )
        goto gotoExample_string1;
    if ( !(string2 = (char*) calloc(STRING_MAX, sizeof(char))) )
        goto gotoExample_string2;
    if ( !(string3 = (char*) calloc(STRING_MAX, sizeof(char))) )
        goto gotoExample_string3;
    if ( !(string4 = (char*) calloc(STRING_MAX, sizeof(char))) )
        goto gotoExample_string4;
    if ( !(string5 = (char*) calloc(STRING_MAX, sizeof(char))) )
        goto gotoExample_string5;

    //important code goes here

gotoExample_string5:
    free(string4);
gotoExample_string4:
    free(string3);
gotoExample_string3:
    free(string2);
gotoExample_string2:
    free(string1);
gotoExample_string1:
}

void nestedIfExample()
{
    char *string1, *string2, *string3, *string4, *string5;

    if (string1 = (char*) calloc(STRING_MAX, sizeof(char))) 
    {
        if (string2 = (char*) calloc(STRING_MAX, sizeof(char)))
        {
            if (string3 = (char*) calloc(STRING_MAX, sizeof(char)))
            {
                if (string4 = (char*) calloc(STRING_MAX, sizeof(char)))
                {
                    if (string5 = (char*) calloc(STRING_MAX, sizeof(char)))
                    {
                        //important code here
                        free(string5);
                    }
                    free(string4);
                }
                free(string3);
            }
            free(string2);
        }
        free(string1);
    }
}


int main(int argc, char* argv[])
{
    nestedIfExample();
    gotoExample();
    return 0;
}

Я также хотел бы процитировать Линуса Торвальдса на перейти операторы внутри Linux ядро.

и иногда структура плохо, и встает на пути и использует " goto" просто намного яснее.

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

в этом случае у вас есть два возможности

  • используйте goto и будьте счастливы, так как он не обеспечивает вложенность

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

  • дублируйте код и перепишите его в форме вложенности, чтобы вы могли
    используйте структурированные прыжки.

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

язык Pascal является простым примером о последней проблеме. Потому что не имеет оператора "break" , петли в (традиционном) Паскале заканчиваются часто, глядя, как дерьмо, потому что У вас есть чтобы добавить полностью произвольный логично сказать: "с меня хватит".

Is перейти приемлемо для управления ресурсами? Должен ли я использовать вложенные if заявления, или есть ли лучший способ?

обновление: примеры хороших Gotos в C

11 ответов


очистка с помощью goto имеет то преимущество, что он менее подвержен ошибкам. Необходимость освободить каждый ресурс, выделенный на каждую точку возврата, может привести к тому, что кто-то однажды пропустит некоторую очистку при выполнении ремонтных работ.

тем не менее, я процитирую "структурированное Программирование кнута с инструкциями goto":

Я выступаю за исключение go to в некоторых случаях, и за их введение в других.

и цитата кнута Дийкстры в той же статье:


несомненно, Дийкстра был грозной личностью в мире программирования. Его Гото Считается Вредным бумага был слишком раздут. Да, GoTo может использоваться без разбора и может быть вредным, но многие думают, что прямой запрет на GoTo не оправдано. Кнут предоставил очень хорошо аргументированное опровержение Дийкстре в: структурированное Программирование с GO TOs

прочитайте бумагу кнута, вы найдете что ваша картина Гото одно из хороших польз для идти к.

кстати, Dijkstra очень цитируемый для ряда других вещей тоже. Как насчет:

  • объектно-ориентированное программирование-исключительно плохая идея, которая могла возникнуть только в Калифорнии.

использовать Гото только с разумом и структурой. Используйте их редко. Но используйте их.


если с помощью goto вы можете избежать написания сложного кода, а затем использовать goto.

ваш пример также может быть написан так (нет gotos):

void anotherExample()
{
    char *string1, *string2, *string3, *string4, *string5;
    string1 = string2 = string3 = string4 = string5 = 0;
    if ((string1 = (char*) calloc(STRING_MAX, sizeof(char)))
     && (string2 = (char*) calloc(STRING_MAX, sizeof(char)))
     && (string3 = (char*) calloc(STRING_MAX, sizeof(char)))
     && (string4 = (char*) calloc(STRING_MAX, sizeof(char)))
     && (string5 = (char*) calloc(STRING_MAX, sizeof(char))))
    {
       //important code here
    }

  free(string1);
  free(string2);
  free(string3);
  free(string4);
  free(string5);
}

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


Это моя философия.

серьезно, в некоторых случаях goto разумно, особенно если он просто делает что-то очевидное, например, переходит к общему коду возврата в нижней части функции.


лично я использовал goto таким образом в прошлом. Люди ненавидят его, потому что он напоминает им код спагетти, который они использовали, чтобы писать/поддерживать, или потому что кто-то, кто написал/поддерживал такой код, избил концепцию, что goto-зло в них.

Вы, наверное, могли бы написать что-то приличное без Гото, конечно. Но в таких обстоятельствах вреда не будет.


из двух ваших альтернатив goto, естественно, лучше и приятнее. Но есть третья и лучшая альтернатива: используйте рекурсию!


Я бы структурировал код иначе, чем любой из них. Если бы у меня не было какой-то выдающейся причины поступить иначе, я бы, вероятно, написал код примерно так:

char *strings[5] = {NULL};

int all_good = 1;

for (i=0; i<5 && all_good; i++) {
    strings[i] = malloc(STRING_MAX);
    all_good &= strings[i] != NULL;
}

if (all_good)
    important_code();

for (int i=0; i<5; i++)
    free(strings[i]);

одна очень большая разница между примером в статье, на которую вы ссылаетесь, и кодом, который вы публикуете, заключается в том, что ваши метки gotos <functionName>_<number> и их ярлыки goto cleanup_<thing_to_cleanup>.

вы используете goto line_1324 далее, и код будет отредактирован так, что line_1234 ярлык на линии 47823 ...

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


Если вы знаете, что делаете, и полученный код выглядит чище и читабельнее (я уверен, что это так), то нет абсолютно никаких проблем с использованием goto. Особенно широко используется пример "изящного восстановления сбоя инициализации", который вы показали.

кстати, при написании структурированного кода, который инициализирует 100 вещей, вам понадобится 100 уровней отступов...что просто уродливо.


В C,goto часто является единственным способом приблизительного кода очистки, как деструкторы C++ или Java finally положения. Поскольку это действительно лучший инструмент, который у вас есть для этой цели, я говорю использовать его. Да, это легко злоупотреблять, но так же много программных конструкций. Например, большинство программистов Java не колеблясь выбрасывают исключения, но исключения также легко злоупотреблять, если они используются для чего-то другого, чем отчеты об ошибках, например для управления потоком. Но если вы используете goto явно с целью избежать дублирования кода очистки, тогда можно с уверенностью сказать, что вы, вероятно, не злоупотребляете им.

вы можете найти много совершенно разумное использование goto в ядре Linux, например.


для меня я предпочитаю этот стиль обработки ошибок goto. Взяв фрагмент Ника D еще на один шаг, он использует одну общую метку goto для обработки ошибок.

void gotoExample()
{
    char *string1, *string2, *string3, *string4, *string5;
    string1 = string2 = string3 = string4 = string5 = NULL;

    if ( !(string1 = (char*) calloc(STRING_MAX, sizeof(char))) )
        goto HANDLE_ERROR;
    if ( !(string2 = (char*) calloc(STRING_MAX, sizeof(char))) )
        goto HANDLE_ERROR;
    if ( !(string3 = (char*) calloc(STRING_MAX, sizeof(char))) )
        goto HANDLE_ERROR;
    if ( !(string4 = (char*) calloc(STRING_MAX, sizeof(char))) )
        goto HANDLE_ERROR;
    if ( !(string5 = (char*) calloc(STRING_MAX, sizeof(char))) )
        goto HANDLE_ERROR;

    //important code goes here


HANDLE_ERROR:
  if (string5)
    free(string5);

  if (string4)
    free(string4);

  if (string3)
    free(string3);

  if (string2)
    free(string2);

  if (string1)
    free(string1);

}