在功能结束时跳转到清理时,GOTO被认为是无害的吗?

时间:2011-04-18 11:28:34

标签: c coding-style goto

goto声明已经在几个SO讨论中进行了长时间的讨论(参见thisthat),我当然不希望重振那些激烈的争论。

相反,我想集中讨论goto的单个用例并讨论其价值和可能的替代方案。

考虑以下代码片段,这在(至少我自己的)FSM中很常见:

while (state = next_state()) {
        switch (state) {
                case foo:
                        /* handle foo, and finally: */
                        if (error) goto cleanup;
                        break;
                case bar:
                        /* handle bar, and finally: */
                        if (error) goto cleanup;
                        break;
                /* ...other cases... */
        }
}

return ok;

cleanup:
/* do some cleanup, i.e. free() local heap requests, adjust global state, and then: */
return error;

为了保存goto而在单独的函数中交换清理内容似乎很尴尬。另一方面,我们已被提出谴责尽可能使用goto

我的问题:我的代码示例被认为是好风格吗?
如果没有,是否有可行的替代方案?

请遵守上述goto的具体用法。我不想深入研究关于goto的一般用法的另一个讨论。

10 个答案:

答案 0 :(得分:11)

您对goto的使用是可以的。它没有打破使用goto的两种好方法。

  1. goto必须在源
  2. 中向下(几行)
  3. goto labels的最里面的块必须包含goto语句

答案 1 :(得分:6)

我不会将清理逻辑提取到自己的函数中并从不同的地方调用它,而是考虑将switch语句解压缩到一个单独的函数中并从中返回错误代码。在while循环中,您可以检查返回代码并进行清理并在必要时返回。

如果在交换机和清理逻辑之间共享多个资源,那么我认为goto更适合传递所有这些状态。

答案 2 :(得分:6)

switch时不需要转到。使用switchgoto只会增加复杂性。

while (state) {
        switch (state) {
                case cleanup:
                        /* do some cleanup, i.e. free() local heap requests, adjust global state, and then: */
                        return error;
                case foo:
                        /* handle foo, and finally: */
                        if (error) { state = cleanup; continue; }
                        break;
                case bar:
                        /* handle bar, and finally: */
                        if (error) { state = cleanup; continue; }
                        break;
                /* ...other cases... */
        }
        state = next_state();
}

return ok;

答案 3 :(得分:4)

我会说如果清理代码不能一般化,即它特定于它正在使用的函数,那么goto是一种很好而且干净的方法。

答案 4 :(得分:4)

我在OpenBSD内核中看到了以这种方式使用的goto,特别是在ATA设备驱动程序(one such example)中,我个人认为它是好的风格,因为它有助于准确说明发生了什么以及代码如何匹配相应的FSM。当尝试根据规范验证FSM的功能时,使用goto可以稍微提高清晰度。

答案 5 :(得分:2)

如果在进入while循环之前完成了所有的初始化代码,那么你的二进制文件就没用了,你可以在退出循环时进行清理。如果你的状态机完全按照正确的顺序提供东西,那么为什么不呢,但既然你有状态机,为什么不用它来进行清理?

在初始化几件事情时,我并不反对goto,并且有一个简单的错误处理代码,如here所述。但是如果你经历了设置状态机的麻烦,那么我就看不出使用它们的充分理由了。 IMO,这个问题仍然过于笼统,一个更实用的状态机示例会很有用。

答案 6 :(得分:2)

看看Ben Voigt的回答给了我另一个答案:

while (state = next_state()) {
        switch (state) {
                case foo:
                        /* handle foo, and finally: */
                        /* error is set but not bothered with here */ 
                        break;
                case bar:
                        /* handle bar, and finally: */
                        /* error is set but not bothered with here */
                        break;
                /* ...other cases... */
        }

        if (error) {
                /* do some cleanup, i.e. free() local heap requests, */
                /* adjust global state, and then: */
                return error;
        }
}

return ok;

这样做的缺点是你必须记住,在处理状态之后,如果出现错误,它可能会清理。看起来if结构可能是一个if-else链来处理不同类型的错误。

我没有参加FSM的正式课程,但我认为你发布的代码具有相同的行为。

答案 7 :(得分:1)

如果您只需要一些清理代码,以便能够从过程中的多个位置调用它并且需要访问本地资源,则可以使用语句lambda。在切换逻辑之前定义它,然后在需要清理的地方调用它。 我喜欢这个主意有几个原因: 1)它比goto更酷(这一点很重要) 2)您可以获得逻辑的干净封装,而无需创建外部方法并传递一堆参数,因为lambda可以使用闭包来访问相同的局部变量。

答案 8 :(得分:1)

使用goto通过打破多个嵌套for循环来清理代码非常方便,而不是在每个嵌套中设置标志并检查它。例如,如果您无法打开文件并在嵌套中深入发现它,则只需goto清理段并关闭文件并释放资源即可。您可以在coreutilities工具源中看到此类goto示例。

答案 9 :(得分:1)

我喜欢遵循的一般原则是,应尽可能尝试编写其流程和设计符合问题域(“程序需要做什么”)的代码。编程语言包括控制结构和其他适用于大多数但不是所有问题域的功能。当这些结构符合计划的要求时,应使用这些结构。如果程序的要求与语言的功能不匹配,我更愿意专注于编写表达程序需要做的代码,而不是扭曲代码以适应模式,尽管它们满足其他应用程序的要求,真的不符合正在编写的程序的要求。

在某些情况下,将状态机转换为代码的一种非常自然的方式是,如果例程在状态机运行到某种形式的结论之前不必“退出”,则应该有一个goto标签代表每个州,并使用ifgoto语句进行状态转换。如果所需的状态转换更适合其他控制结构(例如while循环),则使用此类循环将优于goto语句,并且使用switch语句可能会使某些类型“适应”(例如,每次执行时执行例程执行状态转换,而不是要求它立即运行完成)更容易。另一方面,由于switch语句实际上只是伪装成“goto”,因此直接使用goto直接使用switch语句来模仿它可能更清晰。