便携式等同于gcc的__attribute __(清理)

时间:2009-12-01 20:07:38

标签: c gcc garbage-collection scope portability

最近我遇到了一个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结构以及函数的统一内存释放部分。我意识到goto的使用可以避免使用更多嵌套的if / else结构(所以,请不要对goto进行神圣的战争......)并且这个例子是设计的,但事实仍然是这可能是非常有用的功能。

不幸的是,据我所知,这是gcc特有的。我对任何便携式方法感兴趣(如果它们存在的话)。 有没有人用gcc以外的其他方式做过这方面的经验?

修改 似乎便携性没有发挥作用。考虑到这一点,有没有办法在gcc空间的执行此操作?似乎很好的功能是gcc特定的...

6 个答案:

答案 0 :(得分:8)

C中没有可移植的方式。

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

编辑:

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

我认为你会发现清理和try / finally并没有被广泛使用,因为C ++中的隐式支持,它与C“足够接近”,对行为感兴趣的人可以将代码转换为C ++缓解。

答案 1 :(得分:4)

问题的前半部分是可移植的方式。

答案 2 :(得分:0)

我们将利用这样的事实,即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变量的警告,则很可能是因为您的代码路径之一直接进入了销毁功能。
  • 大多数公司和开源项目都不允许您使用此宏。 (这可能是一个轻描淡写的内容。)
  • 此列表不完整。始终测试您的代码,尤其是您在互联网上找到的代码。

这里是使用defer宏的OP代码的扩展版本。您应该可以将此代码直接放入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 :(得分:0)

@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

答案 4 :(得分:-2)

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);
}

答案 5 :(得分:-5)

您可以将boost :: shared_ptr用于此目的。

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