代码之家  ›  专栏  ›  技术社区  ›  sharkin

带有'goto'的流控制宏

  •  6
  • sharkin  · 技术社区  · 16 年前

    两种令人讨厌的结构结合在一起 它是否如听起来那么糟糕,还是可以被视为控制goto使用并提供合理清理策略的好方法?

    在工作中,我们讨论了是否允许在我们的编码标准中使用goto。一般来说,没有人想允许免费使用goto,但有些人对使用它进行清理跳跃持积极态度。如此代码所示:

    void func()
    {
       char* p1 = malloc(16);
       if( !p1 )
          goto cleanup;
    
       char* p2 = malloc(16);
       if( !p2 )
          goto cleanup;
    
     goto norm_cleanup;
    
     err_cleanup:
    
       if( p1 )
          free(p1);
    
       if( p2 )
          free(p2);
    
     norm_cleanup:
    }
    

    void func()
    {
       char* p1 = malloc(16);
       if( !p1 ){
          return;
       }
    
       char* p2 = malloc(16);
       if( !p2 ){
          free(p1);
          return;
       }
    
       char* p3 = malloc(16);
       if( !p3 ){
          free(p1);
          free(p2);
          return;
       }
    }
    

    因此,为了能够使用goto,但仍然清楚地将其与自由使用隔离开来,创建了一组流控制宏来处理该任务。看起来像这样(简化):

    #define FAIL_SECTION_BEGIN int exit_code[GUID] = 0;
    #define FAIL_SECTION_DO_EXIT_IF( cond, exitcode ) if(cond){exit_code[GUID] = exitcode; goto exit_label[GUID];}
    #define FAIL_SECTION_ERROR_EXIT(code) exit_label[GUID]: if(exit_code[GUID]) int code = exit_code[GUID];else goto end_label[GUID]
    #define FAIL_SECTION_END end_label[GUID]:
    

    int func()
    {
       char* p1 = NULL;
       char* p2 = NULL;
       char* p3 = NULL;
    
       FAIL_SECTION_BEGIN
       {
          p1 = malloc(16);
          FAIL_SECTION_DO_EXIT_IF( !p1, -1 );
    
          p2 = malloc(16);
          FAIL_SECTION_DO_EXIT_IF( !p2, -1 );
    
          p3 = malloc(16);
          FAIL_SECTION_DO_EXIT_IF( !p3, -1 );
       }
       FAIL_SECTION_ERROR_EXIT( code )
       {
          if( p3 ) 
             free(p3);
    
          if( p2 ) 
             free(p2);
    
          if( p1 ) 
             free(p1);
    
          return code;
        }
        FAIL_SECTION_END
    
      return 0;
    

    它看起来不错,而且有很多好处,但是,在将其推广到开发中之前,我们应该考虑一下有什么缺点吗?毕竟,它非常注重流量控制。两人都气馁了。在这种情况下,劝阻他们的理由是什么?

    谢谢。

    9 回复  |  直到 6 年前
        1
  •  11
  •   mouviciel    16 年前

    错误处理是以下情况中罕见的情况之一 goto

    但如果我必须维护那个代码,我会非常沮丧 转到 被宏隐藏。

    所以在这种情况下 转到 对我来说没问题,但宏不行。

        2
  •  8
  •   GSerg    16 年前

    使用 goto 转到常见的错误处理程序/清理/退出序列是绝对可以的。

        3
  •  7
  •   anon anon    16 年前

    此代码:

    void func()
    {
       char* p1 = malloc(16);
       if( !p1 )
          goto cleanup;
    
       char* p2 = malloc(16);
       if( !p2 )
          goto cleanup;
    
     cleanup:
    
       if( p1 )
          free(p1);
    
       if( p2 )
          free(p2);
    }
    

    可以合法地写成:

    void func()
    {
       char* p1 = malloc(16);
       char* p2 = malloc(16);
    
        free(p1);
        free(p2);
    }
    

    内存分配是否成功。

    这是有效的,因为如果传递一个NULL指针,free()什么也不做。在设计自己的API以分配和释放其他资源时,您可以使用相同的习语:

    // return handle to new Foo resource, or 0 if allocation failed
    FOO_HANDLE AllocFoo();
    
    // release Foo indicated by handle, - do nothing if handle is 0
    void ReleaseFoo( FOO_HANDLE h );
    

    设计这样的API可以大大简化资源管理。

        4
  •  3
  •   Alex B    16 年前

    清理 goto 是一个常见的C习语 used in Linux kernel *.

    **也许Linus的观点不是一个好论点的最佳例子,但它确实表明 转到 用于相对大型的项目*

        5
  •  3
  •   Martin Fido    16 年前

    如果第一个malloc失败,则清理p1和p2。由于goto,p2没有初始化,可能指向任何东西。我用gcc快速运行了这个程序进行检查,试图释放(p2)确实会导致seg错误。

    在上一个示例中,变量的作用域在大括号内(即它们仅存在于FAIL_SECTION_BEGIN块中)。

    假设代码在没有大括号的情况下工作,您仍然需要在FAIL_SECTION_BEGIN之前初始化所有指向NULL的指针,以避免分段错误。

    我并不反对goto和macros,但我更喜欢Neil Butterworth的想法。。

    void func(void)
    {
        void *p1 = malloc(16);
        void *p2 = malloc(16);
        void *p3 = malloc(16);
    
        if (!p1 || !p2 || !p3) goto cleanup;
    
        /* ... */
    
    cleanup:
        if (p1) free(p1);
        if (p2) free(p2);
        if (p3) free(p3);
    }
    

    或者,如果它更合适的话。。

    void func(void)
    {
        void *p1 = NULL;
        void *p2 = NULL;
        void *p3 = NULL;
    
        p1 = malloc(16);
        if (!p1) goto cleanup;
    
        p2 = malloc(16);
        if (!p2) goto cleanup;
    
        p3 = malloc(16);
        if (!p3) goto cleanup;
    
        /* ... */
    
    cleanup:
        if (p1) free(p1);
        if (p2) free(p2);
        if (p3) free(p3);
    }
    
        6
  •  2
  •   Henk Holterman    16 年前

    术语“结构化编程”,我们都知道它是反goto的东西,最初是作为一堆使用goto(或JMP)的编码模式开始和发展的。这些模式被称为 while if 除其他外。

    所以,如果你正在使用goto,请以结构化的方式使用它们。这限制了损害。这些宏观政策似乎是一种合理的方法。

        7
  •  2
  •   Jonathan Leffler    16 年前

    goto 如果你小心的话。在这种情况下,您可以在必要之前有效地声明变量:

    void func()
    {
        char *p1 = 0;
        char *p2 = 0;
        char *p3 = 0;
    
        if ((p1 = malloc(16)) != 0 &&
            (p2 = malloc(16)) != 0 &&
            (p3 = malloc(16)) != 0)
        {
            // Use p1, p2, p3 ...
        }
        free(p1);
        free(p2);
        free(p3);
    }
    

    当每次分配操作后都有大量工作时,您可以在第一个分配操作之前使用标签 free() 操作,以及a 转到 是可以的-错误处理是使用的主要原因 转到 如今,任何其他事情都有点可疑。

    我查看了一些确实有嵌入goto语句的宏的代码。第一次遇到时,看到一个标签被可见代码“未引用”,但无法删除,这很令人困惑。我宁愿避免这种做法。当我不需要知道宏做什么时,宏是可以的——它们只是做而已。当你必须知道它们扩展到什么才能准确使用时,宏就不那么好了。如果他们不向我隐瞒信息,他们更像是一种麻烦,而不是一种帮助。

    插图-为保护罪犯而伪装的名字:

    #define rerrcheck if (currval != &localval && globvar->currtub &&          \
                        globvar->currtub->te_flags & TE_ABORT)                 \
                        { if (globvar->currtub->te_state)                      \
                             globvar->currtub->te_state->ts_flags |= TS_FAILED;\
                          else                                                 \
                             delete_tub_name(globvar->currtub->te_name);       \
                          goto failure;                                        \
                        }
    
    
    #define rgetunsigned(b) {if (_iincnt>=2)  \
                               {_iinptr+=2;_iincnt-=2;b = ldunsigned(_iinptr-2);} \
                             else {b = _igetunsigned(); rerrcheck}}
    

    有几十种变体 rgetunsigned() 它们有点相似——不同的尺寸和不同的加载器功能。

    使用这些代码的一个地方包含了这个循环——在一个大型交换机的单个案例中,在一个较大的代码块中,有一些小代码块和一些大代码块(结构不是特别好):

            for (i = 0 ; i < no_of_rows; i++)
                {
                row_t *tmprow = &val->v_coll.cl_typeinfo->clt_rows[i];
    
                rgetint(tmprow->seqno);
                rgetint(tmprow->level_no);
                rgetint(tmprow->parent_no);
                rgetint(tmprow->fieldnmlen);
                rgetpbuf(tmprow->fieldname, IDENTSIZE);
                rgetint(tmprow->field_no);
                rgetint(tmprow->type);
                rgetint(tmprow->length);
                rgetlong(tmprow->xid);
                rgetint(tmprow->flags);
                rgetint(tmprow->xtype_nm_len);
                rgetpbuf(tmprow->xtype_name, IDENTSIZE);
                rgetint(tmprow->xtype_owner_len);
                rgetpbuf(tmprow->xtype_owner_name, IDENTSIZE);
                rgetpbuf(tmprow->xtype_owner_name,
                         tmprow->xtype_owner_len);
                rgetint(tmprow->alignment);
                rgetlong(tmprow->sourcetype);
                }
    

    那里的代码中是否包含goto语句并不明显!很明显,对它所来自的代码的罪恶进行全面的解释需要一整天的时间——它们是多种多样的。

        8
  •  1
  •   Community Mohan Dere    8 年前

    在我看来,第一个例子比宏化版本更具可读性。 和 mouviciel 说得比我好多了

        9
  •  0
  •   nelix    16 年前
    #define malloc_or_die(size) if(malloc(size) == NULL) exit(1)
    

    这并不是说你真的可以从失败的malloc中恢复,除非你有值得编写事务系统的软件,如果你这样做了,可以在malloc_or_die中添加回滚代码。

    为了更好地使用goto,请查看使用计算goto的解析调度代码。