这是C语言中goto的一个不错的用例吗?


59

我真的很犹豫要问这个问题,因为我不想“征求辩论,争论,民意测验或扩展讨论”,但是我是C语言的新手,并且希望对语言使用的常见模式有更多了解。

我最近听到对该goto命令有些反感,但最近也发现了一个不错的用例。

像这样的代码:

error = function_that_could_fail_1();
if (!error) {
    error = function_that_could_fail_2();
    if (!error) {
        error = function_that_could_fail_3();
        ...to the n-th tab level!
    } else {
        // deal with error, clean up, and return error code
    }
} else {
    // deal with error, clean up, and return error code
}

如果清理部分非常相似,可以这样写得更漂亮(我认为呢?):

error = function_that_could_fail_1();
if(error) {
    goto cleanup;
}
error = function_that_could_fail_2();
if(error) {
    goto cleanup;
}
error = function_that_could_fail_3();
if(error) {
    goto cleanup;
}
...
cleanup:
// deal with error if it exists, clean up
// return error code

这是gotoC语言中常见或可接受的用例吗?有其他/更好的方法可以做到这一点吗?


3
另请参阅此问题(以及我的回答)。
基思·汤普森

2
除了糖衣之外,编译器还可以保证清理和错误检查,而在C中,您必须手动执行操作,这违反DRY并保证在某些地方您懒得检查错误代码或使用错误的标签或类似的东西。
DeadMG

3
@Izkata:不,重点是要具有在抛出异常时由编译器隐式调用的函数(如析构函数)。
DeadMG 2012年

6
@DeadMG:不一定总是使用其他语言。
本杰明·克洛斯特

13
我认为,使用适当的标签命名,示例可能看起来相当不错:goto hell;
咬到

Answers:


48

goto语句(及其相应的标签)是流控制原语(以及条件执行语句)。那样的话,我的意思是它们在那里,您可以用来构建程序流控制网络。您可以将它们视为对流程图节点之间的箭头建模的模型。

其中有些可以直接进行线性流优化(只需使用一系列基本语句即可)。其他模式最好用可用的结构化编程构造替换。如果看起来像是while循环,请使用while循环,好吗?结构化编程模式肯定至少潜在的意图不是乱七八糟的更清晰的goto说明。

但是C并不包括所有可能的结构化编程构造。(对我来说尚不清楚是否所有相关的东西都被发现了;现在发现的速度很慢,但我犹豫要跳到说所有的东西都被发现了。)在我们所知道的那些东西中,C肯定缺少try/ catch/ finally结构(和例外太)。它还缺少多级break-from-loop。这些是goto可以用来实现的东西。也可以使用其他方案来完成这些操作-我们确实知道C具有足够多的非goto原语-但是这些通常涉及创建标志变量和更复杂的循环或保护条件;数据分析增加了控制分析的纠缠度,使程序更难于整体理解。这也使编译器优化和CPU快速执行变得更加困难(大多数流控制结构- 无疑 goto -非常便宜)。

因此,尽管goto除非有必要就不应该使用它,但是应该知道它存在并且可能需要它,并且如果需要它,也不会感到难过。当被调用的函数返回错误条件时,需要重新分配资源的示例。(即try/ finally。)可以这样写,如果不goto这样做,可能会有其自身的缺点,例如维护它的问题。情况示例:

int frobnicateTheThings() {
    char *workingBuffer = malloc(...);
    int i;

    for (i=0 ; i<numberOfThings ; i++) {
        if (giveMeThing(i, workingBuffer) != OK)
            goto error;
        if (processThing(workingBuffer) != OK)
            goto error;
        if (dispatchThing(i, workingBuffer) != OK)
            goto error;
    }

    free(workingBuffer);
    return OK;

  error:
    free(workingBuffer);
    return OOPS;
}

代码可能会更短,但是足以证明这一点。


4
+1:在C转到在技术上是从来没有“需要” -总是有办法做到这一点,它就会变得混乱.....对于使用goto看在MISRA C.强大的一套准则
mattnz

1
你想不想try/catch/finallygoto,尽管相似,但更普遍的(因为它可以在整个多种功能/模块传播)形式的意大利面条代码,可能使用的try/catch/finally
自闭症

65

是。

例如,它用于linux内核。这是近十年前线程结尾的电子邮件,用黑体显示:

来自:罗伯特·洛夫(Robert Love)
主题:回复:2.6.0测试*有机会吗?
日期:2003

年1月12日17:58:06 -0500 2003 年1月12日,星期日,Rob Wilkens写道:

我说“请不要使用goto”,而是使用“ cleanup_lock”函数,并在所有return语句之前添加它。.这不应该成为负担。是的,它要求开发人员加倍努力,但最终结果是更好的代码。

不,这太过分了,它使内核膨胀。它为N条错误路径内嵌了一堆垃圾,而不是在结尾处只包含一次退出代码。 缓存占用空间是关键,而您刚刚杀死了它。

也不容易阅读。

最后一个论点是,它不能让我们干净地进行通常的堆栈式的wind和unwind,即

        do A
        if (error)
            goto out_a;
        do B
        if (error)
            goto out_b;
        do C
        if (error)
            goto out_c;
        goto out;
        out_c:
        undo C
        out_b:
        undo B:
        out_a:
        undo A
        out:
        return ret;

现在停止。

罗伯特·洛夫

就是说,一旦习惯了使用goto,就需要大量的纪律来阻止自己创建意大利面条式代码,因此,除非您编写的东西需要速度和低内存占用(例如内核或嵌入式系统),否则您应该真正编写第一个goto 之前先考虑一下。


21
请注意,就原始速度和可读性的优先级而言,内核与非内核程序不同。换句话说,他们已经进行了概要分析,发现他们需要使用goto优化代码以提高速度。

11
使用堆栈展开处理错误时进行清理,而无需实际推动堆栈!这是goto的绝佳用法。
mike30 2012年

1
@ user1249,Rubbish,您无法剖析使用一段{library,kernel}代码的每个{past,existing,future}应用程序。您只需要快速。
佩里耶

1
无关:我很惊讶人们如何使用邮件列表来完成所有工作,更不用说如此庞大的项目了。太原始了。人们如何进入信息仓库?
亚历山大

2
我不太关心节制。如果有人足够软,可以被互联网上的混蛋拒之门外,那么没有他们,您的项目可能会更好。我更关注跟上传入消息的浪潮是不切实际的,例如,如何利用很少的工具来跟踪报价就如何进行自然的来回讨论。
亚历山大

14

我认为,您发布的代码是有效使用的示例goto,因为您只能向下跳转,并且只能像原始异常处理程序那样使用它。

但是,由于古老的goto争论,程序员已经回避goto了40年,因此他们不习惯使用goto读取代码。这是避免使用goto的正当理由:这根本不是标准。

我会将代码重写为C程序员更容易阅读的东西:

Error some_func (void)
{
  Error error;
  type_t* resource = malloc(...);

  error = some_other_func (resource);

  free (resource);

  /* error handling can be placed here, or it can be returned to the caller */

  return error;
}


Error some_other_func (type_t* resource)  // inline if needed
{
  error = function_that_could_fail_1();
  if(error)
  {
    return error;
  }

  /* ... */

  error = function_that_could_fail_2();
  if(error)
  {
    return error;
  }

  /* ... */

  return ok;
}

这种设计的优点:

  • 进行实际工作的功能无需将其自身与与其算法无关的任务(例如分配数据)相关联。
  • 对于C程序员而言,该代码看起来不太陌生,因为他们担心goto和标签。
  • 您可以在执行算法的功能之外,将错误处理和重新分配集中在同一位置。函数处理自己的结果没有意义。


9

在Java中,您可以这样做:

makeCalls:  {
    error = function_that_could_fail_1();
    if (error) {
        break makeCalls;
    }
    error = function_that_could_fail_2();
    if (error) {
        break makeCalls;
    }
    error = function_that_could_fail_3();
    if (error) {
        break makeCalls;
    }
    ...
    return 0;  // No error code.
}
// deal with error if it exists, clean up
// return error code

我经常使用。就像我不喜欢goto的一样,在大多数其他C风格的语言中,我都使用您的代码。没有其他好的方法可以做到这一点。(跳出嵌套循环是类似的情况;在Java中,我使用了标签break,在其他地方都使用了goto。)


3
哇,这是一个整洁的控制结构。
Bryan Boettcher

4
这真的很有趣。我通常会考虑在Java中为此使用try / catch / finally结构(抛出异常而不是中断)。
Robz

5
这确实是不可读的(至少对我而言)。如果存在,则例外要好得多。
m3th0dman 2012年

1
@ m3th0dman在这个特定示例(错误处理)中,我同意您的看法。但是在其他(非例外)情况下,这种惯用语可能会派上用场。
Konrad Rudolph

1
异常的代价很高,它们需要生成一个错误,stacktrace和更多的垃圾。此标签中断使检查循环干净退出。除非一个人不在乎内存和速度,否则我在意所有例外。
Tschallacka

8

我认为这一个不错的用例,但是在“错误”不过是布尔值的情况下,有另一种方式可以完成您想要的操作:

error = function_that_could_fail_1();
error = error || function_that_could_fail_2();
error = error || function_that_could_fail_3();
if(error)
{
     // do cleanup
}

这利用了布尔运算符的短路评估。如果“更好”,则取决于您的个人品味以及您如何习惯该惯用语。


1
问题在于,error所有OR运算后,'的值可能变得毫无意义。
詹姆斯(James)

@James:根据您的评论编辑了我的答案
布朗

1
这还不够。如果在第一个功能期间发生错误,我不想执行第二个或第三个功能。
Robz

2
如果用短路评估来表示短路评估,则由于使用按位或而不是逻辑或,因此此处完全没有做这件事。
Christian Rau 2012年

1
@ChristianRau:谢谢,相应地编辑了我的答案
布朗

6

linux样式指南提供了使用goto的具体原因,这些与您的示例一致:

https://www.kernel.org/doc/Documentation/process/coding-style.rst

使用gotos的基本原理是:

  • 无条件陈述更易于理解和遵循
  • 嵌套减少
  • 防止在进行修改时不更新单个出口点而导致错误
  • 节省了编译器的工作,以优化冗余代码;)

免责声明我不应该分享我的工作。这里的示例有些虚构,请耐心接受。

这对内存管理很有用。我最近研究了动态分配内存的代码(例如char *,函数返回的代码)。该函数查看路径并通过解析路径的令牌来确定路径是否有效:

tmp_string = strdup(string);
token = strtok(tmp_string,delim);
while( token != NULL ){
    ...
    some statements, some involving dynamically allocated memory
    ...
    if ( check_this() ){
        free(var1);
        free(var2);
        ...
        free(varN);
        return 1;
    }
    ...
    some more stuff
    ...
    if(something()){
        if ( check_that() ){
            free(var1);
            free(var2);
            ...
            free(varN);
            return 1;
        } else {
            free(var1);
            free(var2);
            ...
            free(varN);
            return 0;
        }
    }
    token = strtok(NULL,delim);
}

free(var1);
free(var2);
...
free(varN);
return 1;

现在对我来说,如果您需要添加以下代码,则以下代码会更好,更易于维护varNplus1

int retval = 1;
tmp_string = strdup(string);
token = strtok(tmp_string,delim);
while( token != NULL ){
    ...
    some statements, some involving dynamically allocated memory
    ...
    if ( check_this() ){
        retval = 1;
        goto out_free;
    }
    ...
    some more stuff
    ...
    if(something()){
        if ( check_that() ){
            retval = 1;
            goto out_free;
        } else {
            retval = 0;
            goto out_free;
        }
    }
    token = strtok(NULL,delim);
}

out_free:
free(var1);
free(var2);
...
free(varN);
return retval;

现在,代码还存在其他各种问题,即N大于10,并且该函数超过450行,并且在某些地方具有10级嵌套。

但是我提供了我的主管来重构它,我做到了,现在它是一堆都很短的函数,而且它们都具有linux风格

int function(const char * param)
{
    int retval = 1;
    char * var1 = fcn_that_returns_dynamically_allocated_string(param);
    if( var1 == NULL ){
        retval = 0;
        goto out;
    }

    if( isValid(var1) ){
         retval = some_function(var1);
         goto out_free;
    }

    if( isGood(var1) ){
         retval = 0;
         goto out_free;
    }

out_free:
    free(var1);
out:
    return retval;
}

如果考虑不带gotos 的等效项:

int function(const char * param)
{
    int retval = 1;
    char * var1 = fcn_that_returns_dynamically_allocated_string(param);
    if( var1 != NULL ){

       if( isValid(var1) ){
            retval = some_function(var1);
       } else {
          if( isGood(var1) ){
               retval = 0;
          }
       }
       free(var1);

    } else {
       retval = 0;
    }

    return retval;
}

对我而言,在第一种情况下,很明显,如果第一个函数返回NULL,则我们不在这里,而在返回0。在第二种情况下,我必须向下滚动才能看到if包含整个函数。授予第一个在样式上向我指示此名称(名称“ out”),第二个在语法上进行指示。第一个更加明显。

另外,我非常喜欢free()在函数末尾使用语句。部分原因是,根据我的经验,free()函数中间的语句很难闻,并向我表明我应该创建一个子例程。在这种情况下,我var1在函数中创建了函数,但无法free()在子例程中创建它,但这就是为什么goto out_freegoto out样式如此实用的原因。

我认为程序员必须长大,认为这goto是邪恶的。然后,当他们足够成熟时,他们应该浏览Linux源代码并阅读linux样式指南。

我应该补充一点,我非常一致地使用这种样式,每个函数都有一个int retval,一个out_free标签和一个out标签。由于风格上的一致性,提高了可读性。

奖励:打破并继续

假设您有一个while循环

char *var1, *var2;
char line[MAX_LINE_LENGTH];
while( sscanf(line,... ){
    var1 = functionA(line,count);
    var2 = functionB(line,count);

    if( functionC(var1, var2){
         count++
         continue;
    }

    ...
    a bunch of statements
    ...

    count++;
    free(var1);
    free(var2);
}

这段代码还有其他问题,但是一件事是continue语句。我想重写整个内容,但是我的任务是以较小的方式对其进行修改。以一种令我满意的方式来重构它可能需要几天的时间,但实际的更改大约需要花半天的时间。问题在于,即使continue我们仍然需要自由var1和自由var2。我必须添加一个var3,这让我想不得不镜像free()语句。

当时我是一个相对较新的实习生,但是前一段时间我一直在看linux源代码,所以我问我的主管我是否可以使用goto语句。他说可以,而我做到了:

char *var1, *var2;
char line[MAX_LINE_LENGTH];
while( sscanf(line,... ){
    var1 = functionA(line,count);
    var2 = functionB(line,count);
    var3 = newFunction(line,count);

    if( functionC(var1, var2){
         goto next;
    }

    ...
    a bunch of statements
    ...
next:
    count++;
    free(var1);
    free(var2);
}

我认为继续充其量是可以的,但对我而言,它们就像带有隐形标签的goto。休息时间也一样。我仍然希望继续或中断,除非像这里的情况那样,它迫使您在多个位置镜像修改。

我还要补充一点,goto next;和的使用next:对我来说并不令人满意。它们仅比镜像free()的和count++语句更好。

goto几乎总是错误的,但是必须知道何时可以使用它们。

我没有讨论的一件事是错误处理,该问题已被其他答案所涵盖。

性能

可以看看strtok()的实现http://opensource.apple.com//source/Libc/Libc-167/string.subproj/strtok.c

#include <stddef.h>
#include <string.h>

char *
strtok(s, delim)
    register char *s;
    register const char *delim;
{
    register char *spanp;
    register int c, sc;
    char *tok;
    static char *last;


    if (s == NULL && (s = last) == NULL)
        return (NULL);

    /*
     * Skip (span) leading delimiters (s += strspn(s, delim), sort of).
     */
cont:
    c = *s++;
    for (spanp = (char *)delim; (sc = *spanp++) != 0;) {
        if (c == sc)
            goto cont;
    }

    if (c == 0) {       /* no non-delimiter characters */
        last = NULL;
        return (NULL);
    }
    tok = s - 1;

    /*
     * Scan token (scan for delimiters: s += strcspn(s, delim), sort of).
     * Note that delim must have one NUL; we stop if we see that, too.
     */
    for (;;) {
        c = *s++;
        spanp = (char *)delim;
        do {
            if ((sc = *spanp++) == c) {
                if (c == 0)
                    s = NULL;
                else
                    s[-1] = 0;
                last = s;
                return (tok);
            }
        } while (sc != 0);
    }
    /* NOTREACHED */
}

如果我错了,请指正我,但我相信cont:标签和goto cont;语句的存在是为了提高性能(它们肯定不会使代码更具可读性)。通过执行以下操作,可以将其替换为可读代码

while( isDelim(*s++,delim));

跳过定界符。但是为了尽可能快并避免不必要的函数调用,他们是这样做的。

我读了Dijkstra的论文,发现它很深奥。

google“ dijkstra goto声明被认为是有害的”,因为我没有足够的声誉来发布两个以上的链接。

我已经看到它被引用为不使用goto的原因,并且阅读它对我对goto的使用没有任何改变。

附录

在考虑所有有关持续和中断的问题时,我想出了一条整洁的规则。

  • 如果在while循环中有一个continue,则while循环的主体应该是一个函数,而continue应该是一个return语句。
  • 如果在while循环中有一个break语句,则while循环本身应该是一个函数,而break应该成为return语句。
  • 如果两者都存在,则可能出了问题。

由于范围问题,这并不总是可能的,但是我发现这样做可以使我的代码推理变得容易得多。我注意到,只要while循环中断或继续,都会给我一种不好的感觉。


2
+1,但我可能不同意一点吗?“我认为程序员必须从小就相信goto是邪恶的。” 也许是这样,但是我于1975年首先学会了使用BASIC进行行号和GOTO编程,而没有使用文本编辑器。十年后,我遇到了结构化编程,此后我花了一个月的时间才停止自己使用GOTO,任何停止的压力。今天,出于各种原因,我每年都会使用GOTO几次,但是使用率并不高。没有长大就认为GOTO是邪恶的,并没有给我造成我所知道的任何伤害,甚至可能对我们有好处。这就是我。
2016年

1
我认为您是对的。当我想到不要使用GOTO的想法时,碰巧的是,我当时正在研究Linux源代码,当时我正在研究具有这些功能且具有多个退出点并可以释放内存的代码。否则,我将永远不会知道这些技术。
菲利普·卡芬

1
@thb另外,一个有趣的故事,我当时问我的主管,是允许使用GOTO的实习生,我确保向他解释说我将以一种特定的方式使用它们,例如在GOTO中使用的方式。 Linux内核,他说:“好吧,这很有道理,而且我不知道您可以在C语言中使用GOTO。”
菲利普·卡芬

1
@thb我不知道像这样进入循环(而不是破坏循环)是否好?好吧,这是一个不好的例子,但是我发现Knuth的结构化编程中带有 goto语句的goto语句(示例7a)与go语句的快速排序不是很容易理解。
Yai0Phah

@ Yai0Phah我将解释我的观点,但我的解释不会减少您的好例子7a!我赞成这个例子。尽管如此,专横的大二学生还是喜欢向人们讲解goto。自1985年以来,很难找到实际使用goto会造成严重麻烦的方法,而人们却可以找到无害的goto来简化程序员的工作。无论如何,Goto在现代编程中很少出现,当它出现时,我的建议是,如果您想使用它,那么您可能应该只使用它。后藤很好。goto的主要问题在于,有些人认为弃用 goto会使它们看起来很聪明。
THB

5

我个人将其重构为:

int DoLotsOfStuffThatCouldFail (paramstruct *params)
{
    int errcode = EC_NOERROR;

    if ((errcode = FunctionThatCouldFail1 (params)) != EC_NOERROR) return errcode;
    if ((errcode = FunctionThatCouldFail2 (params)) != EC_NOERROR) return errcode;
    if ((errcode = FunctionThatCouldFail3 (params)) != EC_NOERROR) return errcode;
    if ((errcode = FunctionThatCouldFail4 (params)) != EC_NOERROR) return errcode;

    return EC_NOERROR;
}

void DoStuff (paramstruct *params)
{
    int errcode = EC_NOERROR;

    InitStuffThatMayNeedToBeCleaned (params);

    if ((errcode = DoLotsOfStuffThatCouldFail (params)) != EC_NOERROR)
    {
         CleanupAfterError (params, errcode);
    }
}

避免深层嵌套比避免goto更有动机(IMO导致第一个代码示例的问题更严重),并且当然取决于CleanupAfterError是否可能超出范围(在这种情况下,“ params”可以是一个结构,其中包含一些您需要释放的已分配内存,一个您需要关闭的FILE *等等。

我看到的这种方法的一个主要优点是,在FTCF2和FTCF3之间放置一个假设的未来额外步骤(或删除现有的现有步骤)既容易又清洁,因此它更易于维护(包括继承了我的代码,不想私刑!)-放在一边,嵌套版本缺少该代码。


1
我没有在问题中说明这一点,但是FTCF可能没有相同的参数,这会使此模式更加复杂。不过谢谢
罗布

3

查看MISRA(汽车工业软件可靠性协会)C编码指南,该指南允许严格的条件下进行goto(符合您的示例)

在我工作的地方,将编写相同的代码-无需执行goto-在任何软件公司中,避免对它们进行不必要的宗教辩论是一大利好。

error = function_that_could_fail_1();
if(!error) {
  error = function_that_could_fail_2();
}
if(!error) {
  error = function_that_could_fail_3();
} 
if(!error) {
...
if (error) {
  cleanup:
} 

或“ goto拖入” –比goto还要狡猾,但却绕过了“ No goto Ever!”!营)“当然一定可以,不使用后藤” ....

do {
  if (error = function_that_could_fail_1() ){
    break 
  }
  if (error = function_that_could_fail_2() ){
    break 
  }
  ....... 
} while (0) 
cleanup();
.... 

如果这些函数具有相同的参数类型,请将它们放入表中并使用循环-


2
目前MISRA-C:2004年指南并没有允许转到以任何形式(见规则14.4)。请注意,MISRA委员会一直对此感到困惑,他们不知道该站哪只脚。首先,他们无条件禁止使用goto,continue等。但是在即将到来的MISRA 2011草案中,他们希望再次允许使用它们。作为旁注,请注意,MISRA禁止在if语句内进行赋值,这有很好的理由,因为它比使用goto更为危险。

1
从分析的角度来看,向程序添加标志等同于复制标志所在范围内的所有代码,使if(flag)一个副本中的每个代码都具有“ if”分支,而另一副本中的每个对应语句都具有“其他”。设置和清除标志的动作实际上是在这两个版本的代码之间跳转的“陷阱”。有时候使用标志比使用其他标志更干净,但是添加标志以保存一个goto目标并不是一个好的折衷。
超级猫

1

我还使用goto其他do/while/continue/break黑客的可读性是否较低

goto的优势在于,它们的目标都有名称,并且可以读取goto something;。这可能比更具可读性break或者continue如果你没有实际停止或继续的东西。


4
一个do ... while(0)或另一个结构内的任何地方,它不是一个实际的循环,而是试图防止使用goto
aib 2012年

1
啊,谢谢,我不知道这个特殊的品牌“为什么有人会那样做?!” 构造呢。
本杰明·克洛斯特

2
通常,仅在包含do / while / continue / break黑客的模块太长的时候才变得不可读。
John R. Strohm

2
我找不到任何作为使用goto的理由。中断并继续会有明显的后果。转到...哪里?标签在哪里?Break and go会告诉您确切的位置以及下一步的位置。
钻机2012年

1
标签当然应该在循环内可见。我同意@John R. Strohm的评论的冗长部分。然后,您的意思翻译为循环黑客,变成“突破了?这不是循环!”。无论如何,这已成为OP担心的事情,因此我放弃了讨论。
2012年

-1
for (int y=0; y<height; ++y) {
    for (int x=0; x<width; ++x) {
        if (find(x, y)) goto found;
    }
}
found:

如果只有一个循环,则break工作原理与完全相同goto,但没有污点。
9000 2012年

6
-1:首先,x和y超出范围:找到,所以这对您没有任何帮助。其次,使用已编写的代码,您发现了一个事实:并不意味着您已找到所需的内容。
John R. Strohm

因为这是我打破多个循环时可以想到的最小示例。请随时对其进行编辑,以获得更好的标签或完成检查。
aib 2012年

1
但也要记住,C函数不一定是无副作用的。
aib 2012年

1
@ JohnR.Strohm这没有什么意义...'found'标签用于中断循环,而不是检查变量。如果我想检查变量,可以执行以下操作:for(int y = 0; y <height; ++ y){for(int x = 0; x <width; ++ x){if(find( x,y)){doSomeThingWith(x,y); 转到 }}}发现:
YoYoYonnY

-1

总会有阵营说一种方式是可以接受的,而另一种方式是不可接受的。我工作过的公司皱着眉头或强烈反对使用goto。就个人而言,我想不起曾经用过一次,但这并不意味着它们不好,这只是另一种处理方式。

在C语言中,我通常执行以下操作:

  • 测试可能阻止处理(不良输入等)和“退货”的条件
  • 执行所有需要资源分配的步骤(例如mallocs)
  • 执行处理,其中多个步骤检查是否成功
  • 释放任何资源(如果已成功分配)
  • 返回任何结果

对于处理,使用您的goto示例,我将这样做:

错误= function_that_could_fail_1(); if(!error){error = function_that_could_fail_2(); } if(!error){error = function_that_could_fail_3(); }

没有嵌套,并且在if子句中,如果该步骤生成了错误,则可以执行任何错误报告。因此,它不必比使用gotos的方法“更糟糕”。

我还没有遇到过这样的情况,即某人有其他方法无法完成的事情,并且具有可读性/可理解性,这就是关键,恕我直言。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.