最近我遇到了一个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特定的...
答案 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
。使用break
或return
将泄漏一个或多个对象。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);