在C中有效使用goto进行错误管理?


92

这个问题实际上是一个有趣的讨论的结果前一段时间在programming.reddit.com上。基本上可以归结为以下代码:

int foo(int bar)
{
    int return_value = 0;
    if (!do_something( bar )) {
        goto error_1;
    }
    if (!init_stuff( bar )) {
        goto error_2;
    }
    if (!prepare_stuff( bar )) {
        goto error_3;
    }
    return_value = do_the_thing( bar );
error_3:
    cleanup_3();
error_2:
    cleanup_2();
error_1:
    cleanup_1();
    return return_value;
}

使用goto这里似乎是最好的方法,导致所有可能的代码最整洁,最有效,或者至少在我看来如此。在代码完成中引用史蒂夫·麦康奈尔:

goto在例程中很有用,该例程分配资源,对这些资源执行操作,然后重新分配资源。使用goto,您可以在代码的一部分中进行清理。跳转减少了忘记检测到错误的每个位置重新分配资源的可能性。

这种方法的另一种支持来自Linux设备驱动程序本部分一书。

你怎么看?这种情况goto在C中是否有效?您是否希望使用其他方法来产生更复杂的代码和/或更低效率的代码,但是避免使用goto



@Eli:为什么不删除标签并将函数(cleanup_3();)放在if的括号中?

@Akito:你什么意思?您能否将您的建议作为答案附带完整的代码示例?
伊莱·班德斯基

@EliBendersky:请看我的答案。

我最讨厌Visual Basic(包括VBS和VB6)的一件事是on error goto error错误处理系统:)
Manu343726 2013年

Answers:


61

FWIF,我发现您在问题示例中给出的错误处理惯用法比到目前为止答案中给出的任何替代方法更具可读性和更易于理解。虽然这goto通常是一个坏主意,但以简单统一的方式进行错误处理可能会很有用。在这种情况下,即使它是a goto,它也已以定义良好或几乎没有结构化的方式使用。


1
如果不是,他是否只能删除这些标签并将其直接放入函数中?是否更具可读性?

8
@StartupCrazy,我知道这岁了,但是为了确保此站点上帖子的合法性,我会指出,不,他不能。如果他在代码中的goto error3上遇到错误,他将运行清理1 2和3,在您建议的解决方案中,他将仅运行清理3。他可以嵌套一些东西,但这只是箭头反模式,程序员应该避免这种情况。 。
gbtimmon 2012年

17

一般来说,避免goto是一个好主意,但是如今,当Dijkstra首次撰写“ GOTO被认为有害”时,普遍存在的滥用甚至没有引起大多数人的选择。

您概述的是错误处理问题的通用解决方案-只要仔细使用,对我来说就很好。

您的特定示例可以简化如下(步骤1):

int foo(int bar)
{
    int return_value = 0;
    if (!do_something(bar)) {
        goto error_1;
    }
    if (!init_stuff(bar)) {
        goto error_2;
    }
    if (prepare_stuff(bar))
    {
        return_value = do_the_thing(bar);
        cleanup_3();
    }
error_2:
    cleanup_2();
error_1:
    cleanup_1();
    return return_value;
}

继续该过程:

int foo(int bar)
{
    int return_value = 0;
    if (do_something(bar))
    {   
        if (init_stuff(bar))
        {
            if (prepare_stuff(bar))
            {
                return_value = do_the_thing(bar);
                cleanup_3();
            }
            cleanup_2();
        }
        cleanup_1();
    }
    return return_value;
}

我相信,这相当于原始代码。这看起来特别干净,因为原始代码本身非常干净且组织良好。通常,代码片段并不那么整洁(尽管我接受它们应该是的论点)。例如,传递给初始化(设置)例程的状态常常多于所显示的状态,因此传递给清除例程的状态也更多。


23
是的,嵌套解决方案是可行的替代方案之一。不幸的是,随着嵌套层次的加深,它变得不那么可行了。
Eli Bendersky

4
@eliben:同意-但更深层的嵌套可能(可能是)表明您需要引入更多的功能,或者让准备步骤做更多的工作,或者以其他方式重构您的代码。我可能会争辩说,每个prepare函数都应进行设置,调用链中的下一个并进行自己的清理。它将工作本地化-您甚至可以节省三个清理功能。它还部分取决于在任何其他调用序列中是否使用(可用)任何设置或清除功能。
乔纳森·莱夫勒

6
不幸的是,这不适用于循环-如果循环内发生错误,则goto比设置和检查标志和'break'语句(巧妙地伪装了goto)的选择要干净得多。
亚当·罗森菲尔德

1
@Smith,更像是没有防弹背心的驾驶。
斯特拉格

6
我知道我在这里处死法,但是我发现这个建议相当糟糕- 应该避免使用箭头反模式
KingRadical

16

令我惊讶的是,没有人建议使用这种替代方法,因此即使这个问题已经存在了一段时间,我也将其添加进来:解决此问题的一种好方法是使用变量来跟踪当前状态。无论是否goto用于获取清除代码,均可使用此技术。像任何编码技术一样,它也有其优点和缺点,并且并不适合每种情况,但是如果您选择一种样式,则值得考虑-尤其是如果您希望避免goto以深层嵌套ifs 结尾的话。

基本思想是,对于可能需要执行的每个清理操作,都有一个变量,可以根据该变量的值来判断是否需要执行清理。

我将goto首先显示版本,因为它更接近原始问题中的代码。

int foo(int bar)
{
    int return_value = 0;
    int something_done = 0;
    int stuff_inited = 0;
    int stuff_prepared = 0;


    /*
     * Prepare
     */
    if (do_something(bar)) {
        something_done = 1;
    } else {
        goto cleanup;
    }

    if (init_stuff(bar)) {
        stuff_inited = 1;
    } else {
        goto cleanup;
    }

    if (prepare_stuff(bar)) {
        stufF_prepared = 1;
    } else {
        goto cleanup;
    }

    /*
     * Do the thing
     */
    return_value = do_the_thing(bar);

    /*
     * Clean up
     */
cleanup:
    if (stuff_prepared) {
        unprepare_stuff();
    }

    if (stuff_inited) {
        uninit_stuff();
    }

    if (something_done) {
        undo_something();
    }

    return return_value;
}

相对于其他一些技术,此方法的一个优点是,如果初始化函数的顺序发生了更改,则仍然会进行正确的清理-例如,使用switch另一个答案中描述的方法,如果初始化的顺序发生了更改,则switch必须非常仔细地进行编辑,以免尝试清理最初并未真正初始化的内容。

现在,有些人可能会争辩说,此方法添加了很多额外的变量-在这种情况下的确是这样-但实际上,通常已有的变量已经在跟踪或可以用来跟踪所需的状态。例如,如果prepare_stuff()实际上是对malloc()或的调用open(),则可以使用保存返回的指针或文件描述符的变量-例如:

int fd = -1;

....

fd = open(...);
if (fd == -1) {
    goto cleanup;
}

...

cleanup:

if (fd != -1) {
    close(fd);
}

现在,如果我们另外使用变量跟踪错误状态,则可以避免 goto完全并且仍然可以正确清理,而无需使缩进随着我们需要的更多初始化而变得越来越深:

int foo(int bar)
{
    int return_value = 0;
    int something_done = 0;
    int stuff_inited = 0;
    int stuff_prepared = 0;
    int oksofar = 1;


    /*
     * Prepare
     */
    if (oksofar) {  /* NB This "if" statement is optional (it always executes) but included for consistency */
        if (do_something(bar)) {
            something_done = 1;
        } else {
            oksofar = 0;
        }
    }

    if (oksofar) {
        if (init_stuff(bar)) {
            stuff_inited = 1;
        } else {
            oksofar = 0;
        }
    }

    if (oksofar) {
        if (prepare_stuff(bar)) {
            stuff_prepared = 1;
        } else {
            oksofar = 0;
        }
    }

    /*
     * Do the thing
     */
    if (oksofar) {
        return_value = do_the_thing(bar);
    }

    /*
     * Clean up
     */
    if (stuff_prepared) {
        unprepare_stuff();
    }

    if (stuff_inited) {
        uninit_stuff();
    }

    if (something_done) {
        undo_something();
    }

    return return_value;
}

同样,对此有潜在的批评:

  • 难道所有这些“如果”都会影响绩效吗?否-因为在成功的情况下,您仍然必须进行所有检查(否则,您不会检查所有的错误情况);在失败的情况下,大多数编译器会优化失败的顺序if (oksofar)检查为一次跳转到清除代码的过程(GCC肯定是这样)-在任何情况下,错误的情况通常对于性能而言都不那么重要。
  • 这不是添加另一个变量吗?在这种情况下,是的,但是通常return_value可以使用变量来oksofar扮演这里正在扮演的角色。如果构造函数以一致的方式返回错误,则if在每种情况下都可以避免使用第二种方法:

    int return_value = 0;
    
    if (!return_value) {
        return_value = do_something(bar);
    }
    
    if (!return_value) {
        return_value = init_stuff(bar);
    }
    
    if (!return_value) {
        return_value = prepare_stuff(bar);
    }

    这样的编码的优点之一是一致性,这意味着原始程序员忘记检查返回值的任何地方都像大拇指一样伸出来,这使得查找(一类)错误更加容易。

所以-这是(至今)可以用来解决此问题的另一种样式。正确使用它会产生非常干净,一致的代码-就像任何技术一样,如果使用不当,最终可能会产生冗长而令人困惑的代码:-)


2
看起来您参加聚会迟到了,但是我当然喜欢答案!

Linus可能会拒绝您的代码blogs.oracle.com/oswald/entry/is_goto_the_root_of
Fizz

1
@ user3588161:如果他愿意,那是他的特权-但根据所链接的文章,我不确定是这种情况:请注意,在我描述的样式中,(1)条件句不会嵌套(2)与您所需要的相比,没有其他“ if”语句(假设您要检查所有返回码)。
psmears 2014年

如此之多,而不是可怕的goto甚至更糟糕的箭头反模式解决方案!
顺磁牛角包

8

goto关键字的问题大多被误解了。它不是邪恶的。您只需要了解在每个goto中创建的额外控制路径。很难对您的代码及其有效性进行推理。

FWIW,如果您查找developer.apple.com教程,它们将采用goto方法进行错误处理。

我们不使用gotos。返回值更加重要。异常处理是通过setjmp/longjmp- 尽您所能完成的。


8
尽管在某些情况下我肯定使用过setjmp / longjmp,但我认为它甚至比goto更“糟糕”(我也使用它,但在需要时也有所保留)。我唯一使用setjmp / longjmp的情况是(1)目标将以不受其当前状态影响的方式关闭所有内容,或者(2)目标将重新初始化控制在其中的所有内容setjmp / longjmp保护的块与其当前状态无关。
supercat

4

goto语句在道德上没有什么比(void)*指针在道德上有任何错误的地方。

这完全取决于您使用该工具的方式。在您介绍的(平凡的)案例中,case语句可以实现相同的逻辑,尽管开销更大。真正的问题是,“我的速度要求是多少?”

goto速度很快,特别是如果您要确保将其编译为短跳转时,尤其如此。非常适合需要速度的应用。对于其他应用程序,如果要增加可维护性,则用if / else + case来减轻开销可能是有意义的。

请记住:goto不会杀死应用程序,开发人员会杀死应用程序。

更新:这是案例

int foo(int bar) { 
     int return_value = 0 ; 
     int failure_value = 0 ;

     if (!do_something(bar)) { 
          failure_value = 1; 
      } else if (!init_stuff(bar)) { 
          failure_value = 2; 
      } else if (prepare_stuff(bar)) { 
          return_value = do_the_thing(bar); 
          cleanup_3(); 
      } 

      switch (failure_value) { 
          case 2: cleanup_2(); 
          case 1: cleanup_1(); 
          default: break ; 
      } 
} 

1
您能否提出“案例”替代方案?另外,我认为这与void *的方式不同,后者是C中任何严肃的数据结构抽象所必需的。我认为没有任何人严重反对void *,并且如果没有它,您将找不到单个大型代码库它。
Eli Bendersky

回复:无效*,这正是我的观点,两者在道德上都没有错。下面的开关/案例示例。int foo(int bar){int return_value = 0; int failure_value = 0; 如果(!do_something(bar)){failure_value = 1; }否则if(!init_stuff(bar)){failure_value = 2; }否则if(prepare_stuff(bar)){{return_value = do_the_thing(bar); cleanup_3(); }开关(failure_value){情况2:cleanup_2(); 休息; 情况1:cleanup_1(); 休息; 默认值:break; }
webmarc

5
@webmarc,对不起,但这太可怕了!您刚刚完全模拟了带有标签的goto-发明了自己的标签非描述性值,并使用switch / case实现了goto。Failure_value = 1比“ goto cleanup_something”更干净吗?
Eli Bendersky

4
我觉得您是在这里设置我的...您的问题是有见解的,也是我想要的。但是当我提供答案时,这太可怕了。:-(至于您对标签名称的投诉,它们与示例中的其余部分一样具有描述性:cleanup_1,foo,bar。为什么您攻击的标签名称与问题不相关?
webmarc

1
我无意进行“设置”并引起任何负面情绪,对此感到抱歉!感觉就像您的新方法仅针对“删除goto”,而没有增加任何清晰度。好像您已经重新实现了goto的功能,只是使用了更多不清楚的代码。恕我直言,这不是一件好事-仅仅为了它而摆脱goto。
Eli Bendersky

2

GOTO非常有用。这是您的处理器可以做的事情,这就是为什么您应该有权使用它。

有时您想在函数中添加一些内容,然后单击goto即可轻松完成。可以节省时间。


3
您不需要访问处理器可以完成的每件事。在大多数情况下,goto比替代方案更令人困惑。
David Thornley,2009年

@DavidThornley:是的,您确实需要访问处理器可以执行的每件事,否则,您将浪费处理器。Goto是编程中最好的指令。
罗恩·麦蒙

1

总的来说,我认为可以很清楚地编写一段代码,这是goto一种征兆,即程序流程可能比通常所需的更为复杂。以怪异的方式组合其他程序结构以避免使用goto会尝试治疗症状,而不是疾病。没有以下内容,您的特定示例可能不会很难实施goto

  做{
    ..设置仅在提前退出的情况下才需要清理的thing1
    如果(错误)中断;
    做
    {
      ..设置将在提前退出的情况下需要清理的thing2
      如果(错误)中断;
      // *****查看与此行有关的文本
    } while(0);
    ..清理东西2;
  } while(0);
  ..清理东西1;

但是如果仅在函数失败时进行清理,则goto可以通过return在第一个目标标签之前放置一个来处理该情况。上面的代码需要return在标有的行添加一个*****

在“即使在正常情况下也可以进行清理”的情况下,我认为使用gotodo/ while(0)构造更清晰,因为目标标签本身实际上对“ LOOK AT ME”的呼喊远大于breakdo/ while(0)构造。对于“仅在出错时清除”情况,return从可读性的角度来看语句最终必须位于最差的位置(返回语句通常应位于函数的开头,否则应位于“看起来”类似的位置)。结束); 具有return一个目标标签满足刚刚在此之前的资格更容易地比具有只是一个“循环”的结束之前一个。

顺便说一句,当多个案例的代码共享相同的错误代码时,gotoswitch语句中有时会使用我进行错误处理的一种情况。尽管我的编译器通常很聪明,可以识别出多种情况以同一代码结尾,但我认为这样说更清楚:

 REPARSE_PACKET:
  开关(数据包[0])
  {
    情况PKT_THIS_OPERATION:
      如果(问题条件)
        转到PACKET_ERROR;
      ...处理THIS_OPERATION
      打破;
    案例PKT_THAT_OPERATION:
      如果(问题条件)
        转到PACKET_ERROR;
      ...处理THAT_OPERATION
      打破;
    ...
    案例PKT_PROCESS_CONDITIONALLY
      如果(packet_length <9)
        转到PACKET_ERROR;
      如果(涉及packet [4]的packet_condition)
      {
        packet_length-= 5;
        memmove(数据包,数据包+5,数据包长度);
        转到REPARSE_PACKET;
      }
      其他
      {
        数据包[0] = PKT_CONDITION_SKIPPED;
        packet [4] = packet_length;
        packet_length = 5;
        packet_status = READY_TO_SEND;
      }
      打破;
    ...
    默认:
    {
     PACKET_ERROR:
      packet_error_count ++;
      packet_length = 4;
      数据包[0] = PKT_ERROR;
      packet_status = READY_TO_SEND;
      打破;
    }
  }   

尽管可以用替换goto语句{handle_error(); break;},并且可以使用do/ while(0)循环continue来处理包装的条件执行数据包,但我真的认为这比使用更加清晰goto。此外,虽然有可能从使用过的PACKET_ERROR所有地方复制代码goto PACKET_ERROR,而编译器可能会写出一次重复的代码并用跳转到该共享副本来替换大多数出现的代码,但是使用goto可以更轻松地注意到位置这会使数据包的设置有所不同(例如,如果“有条件执行”指令决定不执行)。


1

我本人是“编写安全关键代码的十至十条规则的力量”的追随者。

我将在其中添加一小段代码,说明我认为对goto的好主意。


规则:将所有代码限制为非常简单的控制流构造–请勿使用goto语句,setjmp或longjmp构造以及直接或间接递归。

原理:更简单的控制流程可以转化为更强大的验证功能,并且通常可以提高代码的清晰度。递归的放逐也许是这里最大的惊喜。但是,如果没有递归,我们可以保证有一个非循环函数调用图,代码分析器可以利用该图,并且可以直接帮助证明应该限制的所有执行实际上都是有界的。(请注意,此规则并不要求所有函数都具有单个返回点-尽管这通常还可以简化控制流程。但是,在很多情况下,早期返回错误是更简单的解决方案。)


取消使用goto 似乎很糟糕,但是:

如果这些规则起初看起来像是Draconian,请记住,它们的目的是使您可以检查代码,从字面上看,您的生活可能取决于其正确性:用于控制您乘坐的飞机的代码,核电站距您居住的地方几英里远的地方,或者是将宇航员带入轨道的航天器。这些规则就像汽车上的安全带一样工作:最初它们可能有点不舒服,但过了一会儿,它们的使用就变成了第二自然,不使用它们就变得难以想象。


22
这样做的问题是,完全消除的通常方法goto是在深层嵌套的if或循环中使用一组“聪明”的布尔值。那真的无济于事。也许您的工具会更好,但是不会,并且您更重要。
Donal Fellows,2010年

1

我同意问题中给出的相反顺序的goto清理是清理大多数功能中最干净的方法。但是我也想指出,有时候,您还是想要清理函数。在这些情况下,如果if(0){label:}惯用语进入清理过程的正确点,我将使用以下变体:

int decode ( char * path_in , char * path_out )
{
  FILE * in , * out ;
  code c ;
  int len ;
  int res = 0  ;
  if ( path_in == NULL )
    in = stdin ;
  else
    {
      if ( ( in = fopen ( path_in , "r" ) ) == NULL )
        goto error_open_file_in ;
    }
  if ( path_out == NULL )
    out = stdout ;
  else
    {
      if ( ( out = fopen ( path_out , "w" ) ) == NULL )
        goto error_open_file_out ;
    }

  if( read_code ( in , & c , & longueur ) )
    goto error_code_construction ;

  if ( decode_h ( in , c , out , longueur ) )
  goto error_decode ;

  if ( 0 ) { error_decode: res = 1 ;}
  free_code ( c ) ;
  if ( 0 ) { error_code_construction: res = 1 ; }
  if ( out != stdout ) fclose ( stdout ) ;
  if ( 0 ) { error_open_file_out: res = 1 ; }
  if ( in != stdin ) fclose ( in ) ;
  if ( 0 ) { error_open_file_in: res = 1 ; }
  return res ;
 }

0

在我看来cleanup_3应该进行清理,然后致电cleanup_2。同样,cleanup_2应该进行清理,然后调用cleanup_1。似乎您每次都需要这样做cleanup_[n]cleanup_[n-1]因此这应该是方法的责任(例如,cleanup_3在没有调用的情况下,永远不能调用它,cleanup_2并且可能导致泄漏)。

使用这种方法,您无需调用gotos,只需调用清除例程,然后返回即可。

不过,这种goto方法并没有不好,只是值得注意的是,它不一定是“最干净的”方法(IMHO)。

如果您正在寻找最佳性能,那么我认为goto解决方案是最佳的。我只希望它在少数几个对性能至关重要的应用程序(例如设备驱动程序,嵌入式设备等)中有意义。否则,这是一种微优化,其优先级低于代码清晰度。


4
那不会削减-清理是特定于资源的,资源仅在此例程中按此顺序分配。在其他地方,它们并不相关,因此彼此调用是没有意义的。
Eli Bendersky

0

我认为,对于给定的代码,这里的问题是错误的。

考虑:

  1. do_something(),init_stuff()和prepare_stuff()似乎知道它们是否失败,因为在这种情况下它们返回false或nil。
  2. 设置状态的责任似乎是这些函数的责任,因为在foo()中没有直接设置状态。

因此:do_something(),init_stuff()和prepare_stuff()应该自己进行清理。有一个单独的cleanup_1()函数在do_something()之后进行清理,这破坏了封装的原理。这是糟糕的设计。

如果他们自己进行清理,则foo()变得非常简单。

另一方面。如果foo()实际上创建了需要拆除的状态,那么goto将是适当的。


0

这是我更喜欢的:

bool do_something(void **ptr1, void **ptr2)
{
    if (!ptr1 || !ptr2) {
        err("Missing arguments");
        return false;
    }
    bool ret = false;

    //Pointers must be initialized as NULL
    void *some_pointer = NULL, *another_pointer = NULL;

    if (allocate_some_stuff(&some_pointer) != STUFF_OK) {
        err("allocate_some_stuff step1 failed, abort");
        goto out;
    }
    if (allocate_some_stuff(&another_pointer) != STUFF_OK) {
        err("allocate_some_stuff step 2 failed, abort");
        goto out;
    }

    void *some_temporary_malloc = malloc(1000);

    //Do something with the data here
    info("do_something OK");

    ret = true;

    // Assign outputs only on success so we don't end up with
    // dangling pointers
    *ptr1 = some_pointer;
    *ptr2 = another_pointer;
out:
    if (!ret) {
        //We are returning an error, clean up everything
        //deallocate_some_stuff is a NO-OP if pointer is NULL
        deallocate_some_stuff(some_pointer);
        deallocate_some_stuff(another_pointer);
    }
    //this needs to be freed every time
    free(some_temporary_malloc);
    return ret;
}

0

但是,旧的讨论......如何使用“箭头反模式”并将以后的每个嵌套级别封装在静态内联函数中呢?该代码看起来很干净,它是最佳的(启用优化时),并且不使用goto。简而言之,分而治之。下面是一个例子:

static inline int foo_2(int bar)
{
    int return_value = 0;
    if ( prepare_stuff( bar ) ) {
        return_value = do_the_thing( bar );
    }
    cleanup_3();
    return return_value;
}

static inline int foo_1(int bar)
{
    int return_value = 0;
    if ( init_stuff( bar ) ) {
        return_value = foo_2(bar);
    }
    cleanup_2();
    return return_value;
}

int foo(int bar)
{
    int return_value = 0;
    if (do_something(bar)) {
        return_value = foo_1(bar);
    }
    cleanup_1();
    return return_value;
}

就空间而言,我们在堆栈中创建了三倍的变量,这并不好,但是在此简单示例中,使用-O2从堆栈中删除变量并使用寄存器时,该变量消失了。我从上面的代码块得到的gcc -S -O2 test.c是以下内容:

    .section    __TEXT,__text,regular,pure_instructions
    .macosx_version_min 10, 13
    .globl  _foo                    ## -- Begin function foo
    .p2align    4, 0x90
_foo:                                   ## @foo
    .cfi_startproc
## %bb.0:
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register %rbp
    pushq   %r14
    pushq   %rbx
    .cfi_offset %rbx, -32
    .cfi_offset %r14, -24
    movl    %edi, %ebx
    xorl    %r14d, %r14d
    xorl    %eax, %eax
    callq   _do_something
    testl   %eax, %eax
    je  LBB0_6
## %bb.1:
    xorl    %r14d, %r14d
    xorl    %eax, %eax
    movl    %ebx, %edi
    callq   _init_stuff
    testl   %eax, %eax
    je  LBB0_5
## %bb.2:
    xorl    %r14d, %r14d
    xorl    %eax, %eax
    movl    %ebx, %edi
    callq   _prepare_stuff
    testl   %eax, %eax
    je  LBB0_4
## %bb.3:
    xorl    %eax, %eax
    movl    %ebx, %edi
    callq   _do_the_thing
    movl    %eax, %r14d
LBB0_4:
    xorl    %eax, %eax
    callq   _cleanup_3
LBB0_5:
    xorl    %eax, %eax
    callq   _cleanup_2
LBB0_6:
    xorl    %eax, %eax
    callq   _cleanup_1
    movl    %r14d, %eax
    popq    %rbx
    popq    %r14
    popq    %rbp
    retq
    .cfi_endproc
                                        ## -- End function

.subsections_via_symbols

-1

我更喜欢使用以下示例中描述的技术...

struct lnode *insert(char *data, int len, struct lnode *list) {
    struct lnode *p, *q;
    uint8_t good;
    struct {
            uint8_t alloc_node : 1;
            uint8_t alloc_str : 1;
    } cleanup = { 0, 0 };

    // allocate node.
    p = (struct lnode *)malloc(sizeof(struct lnode));
    good = cleanup.alloc_node = (p != NULL);

    // good? then allocate str
    if (good) {
            p->str = (char *)malloc(sizeof(char)*len);
            good = cleanup.alloc_str = (p->str != NULL);
    }

    // good? copy data
    if(good) {
            memcpy ( p->str, data, len );
    }

    // still good? insert in list
    if(good) {
            if(NULL == list) {
                    p->next = NULL;
                    list = p;
            } else {
                    q = list;
                    while(q->next != NULL && good) {
                            // duplicate found--not good
                            good = (strcmp(q->str,p->str) != 0);
                            q = q->next;
                    }
                    if (good) {
                            p->next = q->next;
                            q->next = p;
                    }
            }
    }

    // not-good? cleanup.
    if(!good) {
            if(cleanup.alloc_str)   free(p->str);
            if(cleanup.alloc_node)  free(p);
    }

    // good? return list or else return NULL
    return (good? list: NULL);

}

来源:http//blog.staila.com/?p = 114


2
引人注目的代码和箭头反模式(均在示例中进行了展示)都是不必要地使代码复杂化的东西。除了使用“ goto是邪恶的”之外,没有任何理由使用它们。
KingRadical

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.