相当于 gcc 的 __attribute__(cleanup) 的便携式

Posted

技术标签:

【中文标题】相当于 gcc 的 __attribute__(cleanup) 的便携式【英文标题】:Portable equivalent to gcc's __attribute__(cleanup) 【发布时间】:2010-12-22 03:53:33 【问题描述】:

最近我发现了一个非常有用的 gcc 扩展:__attribute__(cleanup)

基本上,这允许您在局部变量退出作用域时为其分配清理调用。例如,给定以下代码部分,在调用 foo 的任何情况下,必须明确维护和处理所有内存。

void foo() 
   char * buff = ...; /* some memory allocation */
   char * buff2 = 0, * buff3 = 0;
   if (! buff) 
      return;
    else 
      buff2 = ...; /* memory allocation */
      if (! buff2) 
         goto clean_exit;
       else 
         /* ... and so on ... */
      
   

clean_exit:
   free (buff);
   free (buff2);
   free (buff3);

但是,通过使用可以缩减为

的扩展
#define clean_pchar_scope __attribute__((cleanup(pchar_free)))

void pchar_free (char ** c)  free (*c); 

void foo () 
   char * buff clean_pchar_scope = ...; /* some memory allocation */
   char * buff2 clean_pchar_scope = 0, * buff3 clean_pchar_scope = 0;
   if (! buff)
      return;
   buff2 = ...; /* memory allocation */
   if (! buff2)
      return;
   /* and so on */

现在所有内存都在范围的基础上回收,而不使用嵌套的 if/else 或 goto 构造以及函数的合并内存释放部分。我意识到对于更嵌套的 if/else 构造,可以避免使用 goto (所以,请不要对 goto 进行圣战......)并且这个例子是人为的,但事实仍然是这可以是非常有用的功能。

不幸的是,据我所知,这是特定于 gcc 的。我对任何可移植的方式来做同样的事情感兴趣(如果它们甚至存在的话)。 有没有人用 gcc 以外的东西做这件事的经验?

编辑: 似乎便携性没有发挥作用。考虑到这一点,有没有办法在 gcc 空间之外 执行此操作?看起来不错的功能是 gcc 特定的......

【问题讨论】:

有些相关:***.com/questions/1602398/… 对于我正在寻找的东西来说,这有点粗粒度。无论哪种情况,该解决方案似乎仍然是 gcc 特定的(对于接受的答案)或与基于类的答案相关的 C++。 我相信 C 委员会正在寻求添加一个标准的 defer 声明来满足您的需求!这里 (gustedt.wordpress.com/2020/12/14/a-defer-mechanism-for-c) 是一篇总结论文的博文 【参考方案1】:

@dpi 表示 GCC、clang 和 ICC 支持 __attribute__(cleanup)

在这种情况下,您可以拥有一个在大多数编译器中扩展为 __attribute__(cleanup) 的宏,并回退到 MSVC 上的 C++ 实现。它看起来像这样:

#if defined(__cplusplus)
  template<class F> struct finally 
    F f;
    ~finally() f();
  ;

#  define WITH_CLEANUP(type, name, cleaner, ...)            \
   type name __VA_ARGS__;
   finally name # cleaner # __COUNTER__ = [&](cleaner(&name));
#elif
#  define WITH_CLEANUP(type, name, cleaner, ...)            \
      type name __attribute__(cleanup(cleaner)) __VA_ARGS__;
#endif

【讨论】:

【参考方案2】:

我们将利用这样一个事实,即 for 循环可以在每次迭代结束时工作,并且只为一次迭代运行循环。我们最后要做的工作是增加计数器并调用函数来破坏对象。 x 参数是您要限制其生命周期的变量,fn 参数是将破坏对象的函数或函数宏。 CONCATENATE 宏只是为了让我们在嵌套 defer 时不会收到“影子变量”警告。

#define CONCATENATE_DETAIL(x, y) x##y
#define CONCATENATE(x, y) CONCATENATE_DETAIL(x, y)
#define defer(x, fn)                      \
  for (int CONCATENATE(i, __LINE__) = 0;  \
       CONCATENATE(i, __LINE__) == 0;     \
       CONCATENATE(i, __LINE__)++, fn((x)))

规则和限制:

defer 宏具有方便的功能,即在嵌套时,它会以相反的顺序破坏对象;这就是 C++ 所做的。 你必须在defer中使用变量之前声明它。 如果您需要提前退出defer 块,您必须使用continue。使用breakreturn 会泄漏一个或多个对象。 如果您收到警告说在初始化之前使用了 x 变量,这很可能是因为您的代码路径之一直接进入了销毁函数。 大多数公司和开源项目都不允许你使用这个宏。 (这可能是轻描淡写。) 此列表不完整。始终测试您的代码,尤其是您在互联网上找到的代码。

这是 OP 代码的扩展版本,使用 defer 宏。您应该可以将此代码直接放入Godbolt.org 并使用它。

#include <stdio.h>
#include <stdlib.h>

#define CONCATENATE_DETAIL(x, y) x##y
#define CONCATENATE(x, y) CONCATENATE_DETAIL(x, y)
#define defer(x, fn)                      \
  for (int CONCATENATE(i, __LINE__) = 0;  \
       CONCATENATE(i, __LINE__) == 0;     \
       CONCATENATE(i, __LINE__)++, fn((x)))

void cleanup(char* p)  free(p); 

void foo(void) 
  char* buff1 = NULL;
  char* buff2 = NULL;
  char* buff3 = NULL;

  defer(buff1, cleanup) 
    buff1 = malloc(64);
    if (buff1 == NULL)
      continue;
    defer(buff2, free) 
      buff2 = malloc(64);
      if (buff2 == NULL)
        continue;
      defer(buff3, free) 
        buff3 = malloc(64);
        if (buff3 == NULL)
          continue;

        buff1[63] = '\0';
        buff2[63] = '\0';
        buff3[63] = '\0';

        puts(buff1);
        puts(buff2);
        puts(buff3);
      
    
  

defer 代码是我自己的,但几年前我在 Github 上看到过类似的东西,但现在找不到了。

【讨论】:

【参考方案3】:

__attribute__(cleanup) 不是 gcc 特定的,clang 和 icc 也支持它,这使得 msvc 成为唯一不支持它的主要编译器(无论如何,它对于现代 C 开发来说毫无用处)。

因此,即使它不在 ISO 标准中,对于大多数实际用途,它也可以被认为是可移植的。

【讨论】:

【参考方案4】:

你问题的前半部分是便携的方法。

【讨论】:

【参考方案5】:
void foo()

        char *buf1 = 0, *buf2 = 0, *buf3 = 0;
        /** more resource handle */

        do 

                if ( buf1 = ... , !buf1 ) break;
                if ( buf2 = ... , !buf2 ) break;
                if ( buf3 = ... , !buf3 ) break;

                /** to acquire more resource */

                /** to do with resource */

         while (0);

        /** to release more resource */

        if (buf3) free(buf3);
        if (buf2) free(buf2);
        if (buf1) free(buf1);

【讨论】:

【参考方案6】:

C 中没有可移植的方式。

幸运的是,这是带有析构函数的 C++ 标准特性。

编辑:

MSVC 似乎有 __try 和 __finally 关键字,它们也可以用于此目的。这与 C++ 异常处理不同,我认为它在 C 中可用。

我想你会发现 cleanup 和 try/finally 并没有被广泛使用,因为 C++ 中的隐式支持,它与 C“足够接近”,对这种行为感兴趣的人可以将他们的代码切换到 C++轻松。

【讨论】:

好的,我已经更新以忽略可移植性问题。除此之外,您是否知道在 gcc 之外执行此操作的方法?【参考方案7】:

您可以为此目的使用 boost::shared_ptr。

boost::shared_ptr<char> buffer(p, cleanup);

【讨论】:

这个问题是 C 特定的。我了解auto_ptrboost:: 设施,不幸的是它们不适用于C。

以上是关于相当于 gcc 的 __attribute__(cleanup) 的便携式的主要内容,如果未能解决你的问题,请参考以下文章

C 语言编程 — GCC Attribute 语法扩展

c++11(或更低版本)中 gcc __attribute__((unused)) 的 Visual Studio 等效项?

GCC __attribute __((模式(XX))实际上做了什么?

Linux gcc支持的语法 __attribute__ 属性设置

GCC 的 __attribute__((__packed__)) 是不是保留原始顺序?

gcc - 如何在结构定义中结合 __attribute__((dllexport)) 和 [[nodiscard]]?