В чем преимущество использования goto в этом коде? - PullRequest
4 голосов
/ 03 ноября 2011
static gboolean
gst_fd_src_start (GstBaseSrc * bsrc)
{
  GstFdSrc *src = GST_FD_SRC (bsrc);

  src->curoffset = 0;

  if ((src->fdset = gst_poll_new (TRUE)) == NULL)
    goto socket_pair;

  gst_fd_src_update_fd (src, -1);

  return TRUE;

  /* ERRORS */
socket_pair:
  {
    GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ_WRITE, (NULL),
        GST_ERROR_SYSTEM);
    return FALSE;
  }
}

почему здесь кто-то использовал goto socket_pair; я не понимаю, почему этот механизм используется? почему мы просто не пишем сообщение об ошибке и не возвращаемся?

примечание: это код плагина gstreamer

Ответы [ 8 ]

11 голосов
/ 03 ноября 2011

В больших подпрограммах в функции может быть несколько точек, которым необходимо goto код обработки ошибок.Эту процедуру следует рассматривать в контексте ее написания в соответствии с соглашениями о коде, которые предписывают общую форму для обработки ошибок.

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

7 голосов
/ 03 ноября 2011

В коде, который вы цитировали, в принципе нет веской причины для этого.

В целом, вы можете иногда увидеть метку «обработки ошибок» в нижней части функции, если функция должна выполнить более одного действия в случае сбоя (например, не просто вернуть код ошибки), или если Стандарты кодирования требуют одного return для каждой функции (некоторые правительственные работы делают это). Затем основной код тела использует goto для запуска обработки ошибок, когда это необходимо. Это что-то вроде try/catch/finally бедного человека.

Так, например:

int someNiftyFunction() {
    int rv = 0;

    acquireSomeResource();

    if (some_failure_condition) {
        rv = -1;
        goto error_out;
    }

    if (some_other_failure_condition) {
        rv = -2;
        goto error_out;
    }

    if (yet)_another_failure_condition) {
        rv = -3;
        goto error_out;
    }

    setUpSuccessStuff();

exit:
    cleanUpSomeResource();
    return rv;

error_out:

    setUpFailureStuff();
    logSomeValuableInfo();
    goto exit;
}

Там, в основном, все от acquireSomeResource() до метки exit: очень приблизительно представляет собой блок try, метка exit: - это finally, а error_out - подвох. Очень грубо : -)

5 голосов
/ 03 ноября 2011

Если это старомодный C, в котором обработка исключений не встроена как языковая структура, этот идиом является хорошим способом для его моделирования.

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

очевидно, что в этом случае есть только 1условие досрочного выхода, но обычно лучше реализовать эту идиому в коде, а не только в функциях, которые ей строго необходимы

3 голосов
/ 03 ноября 2011

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

2 голосов
/ 03 ноября 2011

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

2 голосов
/ 03 ноября 2011

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

0 голосов
/ 03 ноября 2011

Как указывалось в других ответах, в простом примере это мало что дает;его преимущества становятся более очевидными, когда есть несколько ресурсов для добычи и несколько ошибок.Этот псевдокод демонстрирует это:

boolean allocate_resources (ObjectA **a, ObjectB **b, ObjectC **c)
{
    *a = allocate_a();
    if (*a == NULL) {
        LOG_ERROR("failed to allocate A");
        goto fail0;
    }

    *b = allocate_b();
    if (*b == NULL) {
        LOG_ERROR("failed to allocate B");
        goto fail1;
    }

    *c = allocate_c();
    if (*c == NULL) {
        LOG_ERROR("failed to allocate C");
        goto fail2;
    }

    return true;

fail2:
    release_b(*b);
fail1:
    release_a(*a);
fail0:
    return false;
}

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

Сравните это с одной из возможных версий, в которой вместо goto используется вложение:

// Bad style. Don't do this.
boolean allocate_resources (ObjectA **a, ObjectB **b, ObjectC **c)
{
    *a = allocate_a();
    if (*a == NULL) {
        LOG_ERROR("failed to allocate A");
    } else {
        *b = allocate_b();
        if (*b == NULL) {
            LOG_ERROR("failed to allocate B");
        } else {
            *c = allocate_c();
            if (*c == NULL) {
                LOG_ERROR("failed to allocate C");
            } else {
                return true;
            }

            release_b(*b);
        }

        release_a(*a);
    }

    return false;
}

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

Надеюсь, я не испортил его, и он функционально эквивалентен примеру goto;Я предлагаю вам попытаться доказать, что это эквивалентно, и лично убедиться, насколько сложнее работать с ним.

0 голосов
/ 03 ноября 2011

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

static gboolean
gst_fd_src_start (GstBaseSrc * bsrc)
{
  GstFdSrc *src = GST_FD_SRC (bsrc);

  src->curoffset = 0;

  if ((src->fdset = gst_poll_new (TRUE)) == NULL)
  {
GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ_WRITE, (NULL),
    GST_ERROR_SYSTEM);
return FALSE;
  }



  gst_fd_src_update_fd (src, -1);

  return TRUE;
}
...