4 回答
正如我在评论中所说,我的建议是首先避免使用这样的东西。无论您的视频说了什么或暗示了什么,现代 C 程序员的普遍观点是应该尽量减少宏的使用。类变量宏通常应该表示与上下文无关的常量值,类函数宏通常更好地实现为实际函数。这并不是说必须避免所有宏的使用,但是大多数现代 C 专业人士在复杂宏上看起来很糟糕,而且您defer()
的复杂性足以满足要求。
此外,尝试将其他语言的风格和习语导入 C 对您自己没有任何好处。每种语言的常用习语之所以成立,是因为它们适用于该语言,而不是通常因为它们具有内在的内在价值。我建议你学习 C 和 C 程序员使用的习语,而不是如何编写看起来像 Go 的 C 代码。
话虽如此,让我们考虑一下您的defer()
宏。你写,
然而问题是不能通过代码来启动声明新变量的代码段
,但实际上限制比这更强。因为宏start
在逗号表达式 ( start,0
) 中使用参数,所以它本身必须是表达式。不允许任何形式的声明或完整陈述。for
这仅与出现在语句控制块的第一个子句中的表达式间接相关。(这同样适用于end
论点。)
还可能需要注意的是,如果关联语句的执行通过通过or语句end
从块分支出来或通过执行不返回的函数(例如or )终止,则宏扩展为无法评估表达式的代码可能也很重要。此外,与 Go 不同的是,表达式是在提供的语句之后完整计算的——之前没有对它的任何部分进行计算,这可能会让 Go 程序员感到惊讶。这些也是下面介绍的选项的特征。return
goto
exit()
longjmp()
defer
end
如果您只想将start
andend
作为宏参数传递,并且您希望允许声明出现在 中start
,那么您可以这样做:
// Option 1
#define defer(start,end) start; for( \
int macro_var_line(done) = 0; \
!done; \
(macro_var_line(done) += 1), (end))
这从宏的替换文本start
中的for
语句移到可能出现任意 C 代码的位置。但是请注意,任何变量声明都将被限定在最里面的包含块中。
如果您想限制声明的范围,那么还有这个替代方案和变体,我发现它比原来的更直接:
// Option 2
#define defer(start, end, body) { start; body end; }
你会像这样使用它:
defer(FILE *f = fopen("log.txt","a+"), fclose(f), // argument list continues ...
fprintf(f,"Some message, f=%p",f);
);
这有点适合您的特定示例,因为它假设主体是作为零个或多个完整语句的序列(可以包括块、流控制语句等)给出的。如您所见,它还要求将主体作为宏参数传递,而不是出现在宏调用之后,但我认为这是一个优势,因为它有助于识别延迟代码的启动点。
您可以使用“Smart Template Container for C”中的技巧。见链接。
#define c_autovar(declvar, ...) for (declvar, *_c_ii = NULL; !_c_ii; ++_c_ii, __VA_ARGS__)
基本上,您声明一个变量并劫持它的类型以形成一个 NULL 指针。该指针用作保护以确保循环只执行一次。增加 NULL 指针很可能是未定义行为,因为标准只允许形成一个指针,指向一个对象之后,而 NULL 不指向任何对象。但是,它很可能无处不在。
我想你可以通过添加一个全局变量来摆脱UB :
int defer_guard;
并将保护指针设置为defer_guard
增量语句中的指针。
extern int defer_guard;
#define defer_var(declvar, cleanup) \
for (declvar, *_c_ii = NULL; \
!_c_ii; \
_c_ii = (void*)&defer_guard, cleanup)
调用时它会正常工作:
defer_var(FILE *f = fopen("log.txt","a+"), fclose(f))
{
fprintf(f,"Some message, f=%p",f);
}
编辑
实际上,可以派生一个同时接受expression
和declaration
as的宏start
。一个必须使用两个 for
循环而不是一个。
#define DEFER(start, end) \
for (int _done = 0; !_done;) \
for (start; !(_done++); end)
int main() {
DEFER(FILE *f = fopen("log.txt","a+"), fclose(f)) {
fprintf(f,"Some message, f=%p", (void*)f);
}
FILE *f;
DEFER(f = fopen("log.txt","a+"), fclose(f)) {
fprintf(f,"Some message, f=%p", (void*)f);
}
return 0;
}
defer
您可以使用__attribute__((cleanup(...)))
GCC 和 Clang 的特性进行模拟。另请参阅有关ing a variable 的SO questionfree
。
例如:
// the following are some utility functions and macros
#define defer(fn) __attribute__((cleanup(fn)))
void cleanup_free(void* p) {
free(*((void**) p));
}
#define defer_free defer(cleanup_free)
void cleanup_file(FILE** fp) {
if (*fp == NULL) { return; }
fclose(*fp);
}
#define defer_file defer(cleanup_file)
// here's our code:
void foo(void) {
// here's some memory allocation
defer_free int* arr = malloc(sizeof(int) * 10);
if (arr == NULL) { return; }
// some file opening
defer_file FILE* fp1 = fopen("file1.txt", "rb");
if (fp1 == NULL) { return; }
// other file opening
defer_file FILE* fp2 = fopen("file2.txt", "rb");
if (fp2 == NULL) { return; }
// rest of the code
}