在这段代码中使用goto有什么好处?

时间:2011-11-03 12:31:50

标签: c error-handling gstreamer 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)
    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 个答案:

答案 0 :(得分:11)

在较大的例程中,函数中可能有多个点需要goto错误处理代码。这个例程应该在根据代码约定编写的上下文中看到,这些代码约定要求一个通用的错误处理形式。

goto确实应该被认为是危险的,但对于像C这样没有异常处理的语言,它可能是错误处理的最不好的选择。

答案 1 :(得分:7)

在您引用的代码中,基本上没有任何充分的理由。

但是,一般情况下,如果函数在失败时需要执行多项操作(例如,不仅仅是返回错误代码),或者如果函数需要执行多次操作,则有时会在函数底部看到“错误处理”标签。编码标准要求每个功能都有 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。非常粗略。 : - )

答案 2 :(得分:5)

如果这是旧式的C,它没有作为语言结构内置的异常处理,这个习惯用法是模拟它的好方法。

想象有10个早期退出条件。如果你想将每个代码编码为return语句,你必须重复调用GST_ELEMENT_ERROR 10次,而使用goto意味着你只需要把它放一次。

显然在这种情况下只有一个早期退出条件,但通常更好的方法是通过代码实现这个习惯用法,而不仅仅是在严格需要它的函数中

答案 3 :(得分:3)

这是C中用于将错误处理和清理与普通代码分开的常用方法。在其他语言中,您将使用某种结构化异常。这是一种更简洁的C语言。在更复杂的情况下,好处更明显。在许多情况下,必须从不同的地方调用错误处理/清理代码。重构它没有多大意义我认为goto并不是那么邪恶,如果你为了这个缘故在C中使用它。在其他具有更好机制的编程语言中,您当然应该避免使用goto ...

答案 4 :(得分:2)

这可以用于处理套接字错误的公共代码。在这种情况下,仅在一种情况下需要,在这种情况下可能不需要使用 goto ,但仅出于一致性原因(所有代码库的一般方法)。

答案 5 :(得分:2)

如上所述,如果你只看这个功能,那就毫无意义了。但是,如果在退出函数之前有一个具有许多错误情况和一些清除问题的函数,则这种编程是不可避免的,否则您的代码将因所有错误问题或某些清理问题而增加。为了使编程更具结构性,有时所有函数都可以用goto析构函数编写,就像你提到的代码一样。

答案 6 :(得分:0)

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;
}

答案 7 :(得分:0)

正如其他答案所指出的那样,在简单的例子中并没有多少作用;当有多种资源需要获取和多种错误条件时,它的好处更加明显。这个伪代码证明了这一点:

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示例;我建议你试着证明自己是等同的,并亲眼看看你的工作有多难。