2
4

4 回答 4

5

正如我在评论中所说,我的建议是首先避免使用这样的东西。无论您的视频说了什么或暗示了什么,现代 C 程序员的普遍观点是应该尽量减少宏的使用。类变量宏通常应该表示与上下文无关的常量值,类函数宏通常更好地实现为实际函数。这并不是说必须避免所有宏的使用,但是大多数现代 C 专业人士在复杂宏上看起来很糟糕,而且您defer()的复杂性足以满足要求。

此外,尝试将其他语言的风格和习语导入 C 对您自己没有任何好处。每种语言的常用习语之所以成立,是因为它们适用于该语言,而不是通常因为它们具有内在的内在价值。我建议你学习 C 和 C 程序员使用的习语,而不是如何编写看起来像 Go 的 C 代码。

话虽如此,让我们考虑一下您的defer()宏。你写,

然而问题是不能通过代码来启动声明新变量的代码段

,但实际上限制比这更强。因为宏start在逗号表达式 ( start,0) 中使用参数,所以它本身必须是表达式。不允许任何形式的声明或完整陈述。for这仅与出现在语句控制块的第一个子句中的表达式间接相关。(这同样适用于end论点。)

还可能需要注意的是,如果关联语句的执行通过通过or语句end从块分支出来或通过执行不返回的函数(例如or )终止,则宏扩展为无法评估表达式的代码可能也很重要。此外,与 Go 不同的是,表达式是在提供的语句之后完整计算的——之前没有对它的任何部分进行计算,这可能会让 Go 程序员感到惊讶。这些也是下面介绍的选项的特征。returngotoexit()longjmp()deferend

如果您只想将startandend作为宏参数传递,并且您希望允许声明出现在 中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);
);

这有点适合您的特定示例,因为它假设主体是作为零个或多个完整语句的序列(可以包括块、流控制语句)给出的。如您所见,它还要求将主体作为宏参数传递,而不是出现在宏调用之后,但我认为这是一个优势,因为它有助于识别延迟代码的启动点。

于 2021-09-26T16:38:46.523 回答
0

实际上,标准委员会正在努力对某个defer功能进行标准化。论文提案还附带了一个参考实现。这个想法是提出这样一个功能,可以用尽可能少的编译器魔法来实现。

如果一切按计划进行,该功能甚至可以基于 lambdas,如果我们及时将它们放入 C23。

于 2021-09-28T18:42:14.380 回答
0

您可以使用“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);
}

编辑

实际上,可以派生一个同时接受expressiondeclarationas的宏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;
}
于 2021-09-26T15:32:07.403 回答
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
}
于 2021-09-26T15:37:53.273 回答