相当于 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
。使用break
或return
会泄漏一个或多个对象。
如果您收到警告说在初始化之前使用了 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_ptr
和boost::
设施,不幸的是它们不适用于C。以上是关于相当于 gcc 的 __attribute__(cleanup) 的便携式的主要内容,如果未能解决你的问题,请参考以下文章
c++11(或更低版本)中 gcc __attribute__((unused)) 的 Visual Studio 等效项?
GCC __attribute __((模式(XX))实际上做了什么?
Linux gcc支持的语法 __attribute__ 属性设置
GCC 的 __attribute__((__packed__)) 是不是保留原始顺序?
gcc - 如何在结构定义中结合 __attribute__((dllexport)) 和 [[nodiscard]]?