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

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

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

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

Я понимаю, что 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 .

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

Например, довольно часто используются условные выражения, КОТОРЫЕ НЕ ВСТАВЛЯЮТСЯ.

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

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

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

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

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

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

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

Обновление: Примеры хороших Gotos на C < / а>


person Community    schedule 10.11.2010    source источник
comment
обратите внимание, что ваш goto идет не в том месте - если ваш malloc для string1 не работает, вы не хотите освобождать память, которую вы не могли выделить :)   -  person KevinDTimm    schedule 10.11.2010
comment
@KevinDTimm: об отказе выделения памяти сигнализирует нулевой указатель - и передача нулевого указателя на free безвредна (nop). В этом случае заверение вас free только в успешном распределении приводит к (значительной) дополнительной работе.   -  person Jerry Coffin    schedule 11.11.2010
comment
@Jerry - хотя это правда, для многих это сложная парадигма. Освобождение нулевого указателя может быть переключением задачи, поэтому я стараюсь этого не делать (следовательно, освобождаю только успешные выделения)   -  person KevinDTimm    schedule 11.11.2010
comment
@KevinDTimm: Откуда вы взяли, что освобождение нулевого указателя будет иметь какое-либо отношение к переключателям задач?   -  person Jerry Coffin    schedule 11.11.2010
comment
@Jerry - переключение задач - то, что делает ваш мозг, когда он чего-то не понимает.   -  person KevinDTimm    schedule 11.11.2010


Ответы (11)


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

Тем не менее, я процитирую «Структурированное программирование с помощью операторов goto» Кнута:

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

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

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

person ninjalj    schedule 10.11.2010

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

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

Кстати, Дейкстра заслуживает уважения и по ряду других причин. Как насчет:

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

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

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

person NealB    schedule 10.11.2010

Если с помощью 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);
}
person Nick Dandoulakis    schedule 10.11.2010

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


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

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

person DigitalRoss    schedule 10.11.2010
comment
Очистка ресурсов - канонический пример использования goto в C. - person ninjalj; 10.11.2010

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

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

person cdhowie    schedule 10.11.2010
comment
Согласованный. Я использую его, но все равно ненавижу, потому что он напоминает мне, насколько раздражает кодирование на C (по сравнению с C ++). - person Šimon Tóth; 10.11.2010
comment
Ха, извините, вы разговариваете не с тем парнем, если предпочитаете C ++ C.;) - person cdhowie; 10.11.2010
comment
Ой, ну не все могут дойти до следующего уровня :-P - person Šimon Tóth; 10.11.2010
comment
Следующий уровень - java: O - person KevinDTimm; 10.11.2010
comment
Да, язык без какой-либо концепции указателя на функцию, где каждый метод является виртуальным, и у вас не может быть разных реализаций для двух членов интерфейса с одной и той же сигнатурой. Следующий уровень. кашель Ага. - person cdhowie; 10.11.2010
comment
Да ладно - кто-то должен был это сказать (и да, я скучаю по всем этим вещам, но есть вознаграждения) - person KevinDTimm; 10.11.2010
comment
@Kevin Ну вот почему я вообще не люблю java. Он дает вам некоторые улучшения, но намного меньше, чем большинство других языков более высокого уровня, но вы все равно платите довольно большим ударом по производительности и огромными семантическими ограничениями. - person Šimon Tóth; 10.11.2010
comment
@Let_Me_Be - но ведь все так делают, не так ли? ;) - person KevinDTimm; 11.11.2010
comment
@Kevin Ну, компании, которые не хотят платить за хороших программистов или за их дальнейшее образование, используют это. Это хороший язык для кодирования обезьян (лучший язык для такого использования, ИМХО). - person Šimon Tóth; 11.11.2010
comment
@LetMB - кажется резким, я использую много языков (хорошо, java / C / python / objc) и считаю, что каждому из них место. 10 лет java / 25 лет C и только год python и objc). Я не совсем обезьяно-кодировщик - хотя я не K или R, или Bjarne, или Gosling, или ... - person KevinDTimm; 11.11.2010

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

person Armen Tsirunyan    schedule 10.11.2010
comment
Рекурсия элегантна, но я не понимаю, как это возможно при наличии зависимости от ресурсов. - person ; 11.11.2010
comment
@Shiftbit: ресурс может быть глобальным или передаваться как параметр-указатель. - person Armen Tsirunyan; 11.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]);
person Jerry Coffin    schedule 10.11.2010

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

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

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

person Pete Kirkham    schedule 10.11.2010
comment
Использование cleanup_ ‹thing_to_cleanup› могло вызвать коллизии. Добавляя к нему префикс имени функции, я получаю, что каждая метка будет уникальной. Возможно, вы предпочтете двойной префикс ‹functionName› _cleanup_ ‹thing_to_cleanup›? - person ; 11.11.2010
comment
чтобы уточнить, я использую ‹functionName› _ ‹thing_to_cleanup›, а не ‹functionName› _ ‹number› - person ; 11.11.2010
comment
@Shiftbit Не знаю, что вы программируете, но в C99 § 6.2.1 имя метки - единственный вид идентификатора, который имеет область действия. - person Pete Kirkham; 11.11.2010
comment
Интересный. Я буду наблюдать за clean_up ‹thing_to_cleanup› в будущем. - person ; 11.11.2010

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

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

person Mirek Kratochvil    schedule 10.11.2010
comment
100 вещей, вам понадобится 100 уровней отступов ... что просто уродливо. - Точно - person ; 10.11.2010
comment
Если вы пишете структурированный код, который инициализирует 100 элементов, вы используете цикл for. Что-то уже пошло не так в вашем дизайне, если вам нужно инициализировать 100 разных вещей в одной и той же функции. - person Pete Kirkham; 11.11.2010
comment
Я согласен, что 100 вещей - это немного абсурдно. Однако не всегда возможно создать цикл для таких вещей, как дескрипторы файлов, для которых требуются определенные атрибуты. Даже если бы он был вложен 7 или 8 раз, все равно было бы неудобно читать. - person ; 11.11.2010

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

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

person Charles Salvia    schedule 10.11.2010

Лично я предпочитаю этот стиль обработки ошибок 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);

}
person hopia    schedule 30.11.2010