C: Вложенные Ifs или Gotos - PullRequest
9 голосов
/ 10 ноября 2010

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

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

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

Я понимаю, что goto является табу во многих других языках, потому что существуют другие механизмы управления, такие как 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;
}

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

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

Например, довольно часто встречаются условные выражения, которые НЕ ГНЕЗДАЮТСЯ.

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

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

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

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

    Это часто делает код намного МЕНЬШЕ читаемым, сложнее в обслуживании и больше.

Язык Паскаль является ярким примером последней проблемы.Поскольку в нем нет оператора «break», циклы в (традиционном) Pascal часто выглядят как полное дерьмо, потому что вы должны добавить совершенно произвольную логику, чтобы сказать «Я закончил сейчас».

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

Обновление: Примеры хороших Gotos In C

Ответы [ 11 ]

11 голосов
/ 11 ноября 2010

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

Прочтите статью Кнута, вы обнаружите, что ваш шаблон GoTo является одним из полезных применений. для GoTo.

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

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

Дейкстра был великим математиком и внес огромный вклад в информатику. Однако я не думаю, что ему приходилось иметь дело или интересоваться повседневными вещами, которые 99,99% наши программы делают.

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

11 голосов
/ 10 ноября 2010

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

Ваш пример также может быть написан так (без goto s):

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);
}
10 голосов
/ 10 ноября 2010

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

Тем не менее, я процитирую "Структурное программирование Кнута с утверждениями goto":

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

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

«Пожалуйста, не попадайтесь в ловушку веры в то, что я ужасно догматичен в отношении [перехода к утверждению]. У меня неприятное ощущение, что другие делают из этого религию, как будто концептуальные проблемы программирования быть решенным одним трюком, простой формой кодирования! " [29].

5 голосов
/ 10 ноября 2010

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


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

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

3 голосов
/ 10 ноября 2010

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

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

2 голосов
/ 11 ноября 2010

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

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

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

2 голосов
/ 11 ноября 2010

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

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]);
2 голосов
/ 10 ноября 2010

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

1 голос
/ 10 ноября 2010

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

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

1 голос
/ 10 ноября 2010

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...