使用'goto'控制流的宏

sha*_*kin 6 c error-handling coding-style goto flow-control

是的,两个讨厌的结构相结合.它听起来是不是很糟糕,还是被视为控制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:
}
Run Code Online (Sandbox Code Playgroud)

这种使用的巨大好处是你不必最终得到这个代码:

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;
   }
}
Run Code Online (Sandbox Code Playgroud)

特别是在具有许多分配的类似构造函数的函数中,这有时会变得非常糟糕,尤其是当有人必须在中间插入某些东西时.

因此,为了能够使用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]:
Run Code Online (Sandbox Code Playgroud)

我们可以使用如下:

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;
Run Code Online (Sandbox Code Playgroud)

它看起来不错,并带来许多好处,但是,在将其推广到开发之前,是否有任何我们应该考虑的缺点?毕竟非常流量控制和转到:ish.两人都气馁.在这种情况下,有什么理由阻止他们?

谢谢.

mou*_*iel 11

错误处理是一种罕见的情况,当时goto并不是那么糟糕.

但是,如果我必须维护该代码,我将非常沮丧goto被宏隐藏.

所以在这种情况下goto我可以,但不是宏.


GSe*_*erg 8

使用goto去一个常见的错误处理程序/清除/退出顺序是绝对的罚款.


小智 7

这段代码:

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);
}
Run Code Online (Sandbox Code Playgroud)

可以合法地写成:

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

    free(p1);
    free(p2);
}
Run Code Online (Sandbox Code Playgroud)

内存分配是否成功.

这是有效的,因为如果传递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 );
Run Code Online (Sandbox Code Playgroud)

像这样设计API可以大大简化资源管理.