Golang有一个名为defer
的有用的语言 struct ,它允许用户推迟函数的执行,直到周围的函数返回.这对于确保安全地销毁资源,同时保持创建-销毁逻辑紧密相连非常有用.
我对在C99中通过宏实现这一点很感兴趣.以下是我到目前为止编写的内容,以及一些示例代码以显示宏的实际操作:
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <time.h>
#include <math.h>
void* log_malloc(size_t size) {
void* ptr = malloc(size);
printf("Allocated %zu bytes: %p\n", size, ptr);
return ptr;
}
void log_free(void* ptr) {
free(ptr);
printf("Freed %p\n", ptr);
}
typedef void (*DeferFunc)();
typedef struct Defer {
DeferFunc f;
void* p;
} Defer;
#define PRE_DEFER() \
size_t num_defers = 0; \
Defer defers[32];
#define DEFER(func, param) \
defers[num_defers].f = func;\
defers[num_defers].p = param;\
num_defers++;
#define POST_DEFER() \
while(num_defers > 0) { \
defers[num_defers - 1].f(defers[num_defers - 1].p); \
num_defers--; \
}
void test() {
PRE_DEFER();
char* a = log_malloc(10);
DEFER(log_free, a);
char* b = log_malloc(20);
DEFER(log_free, b);
char* c = log_malloc(30);
DEFER(log_free, c);
POST_DEFER();
}
int main() {
test();
return 0;
}
这段代码可以工作,在我的基本基准测试中,与仅手动对析构函数语句进行排序相比,它的性能开销约为10%.但是,它受到可处理的DEFER
个宏调用的数量限制.当然,我可以传入我希望在PRE_DEFER
宏中执行的延迟次数,但这样做可能会很繁琐,而且容易出错,特别是在存在分支逻辑或包含DEFER
条语句的循环的情况下.有没有办法以编程方式在生成的宏代码中填充defers
数组的大小(作为VLA)?
我已经考虑过或不感兴趣的解决方案:
- 只需使用C++.这一问题可以与Raii解决.然而,析构函数逻辑变得隐含,我对C++提供的额外复杂性/特性爬行不感兴趣.
- 使用编译器扩展.GCC(我相信还有clang)有一个可以滥用的
__attribute__ cleanup
语法,执行得很好,但在MSVC中不受支持,并且需要修改提供的析构函数来使用指向变量的指针,而不是变量本身. - 在每个
DEFER
处使用重复的alloca
个呼叫.这确实有效,但速度很慢(比手动排序函数调用或我当前的defer
解决方案慢2倍).
任何帮助都将不胜感激.
Update:个
感谢@DAI指出MSVC的__try
和__finally
编译器构造.使用它们,我得到了以下代码:
#define DEFER(fini_call, body) \
__try{ body } \
__finally{ fini_call; }
void test() {
char* a = log_malloc(10);
DEFER(log_free(a), {
char* b = log_malloc(20);
DEFER(log_free(b), {
char* c = log_malloc(30);
DEFER(log_free(c), {})
})
});
}
这样就完成了工作,省go 了对PRE_DEFER
和POST_DEFER
个宏的需求.但是,它与我的原始宏的语法不兼容.此外,它还导致了我的原始版本中隐藏的嵌套,因为函数的其余部分需要传递到DEFER
宏中.
为了完整起见,我包含了代码的GCC/clang属性((清理))版本.具体情况如下:
#define DEFER(type, name, init_func, fini_func_name) \
type name __attribute__ ((__cleanup__(fini_func_name))) = init_func
void* log_malloc(size_t size) {
void* ptr = malloc(size);
printf("Allocated %zu bytes: %p\n", size, ptr);
return ptr;
}
void log_free(char** ptr) {
free(*ptr);
printf("Freed %p\n", *ptr);
}
int test() {
DEFER(char*, a, log_malloc(10), log_free);
DEFER(char*, b, log_malloc(20), log_free);
DEFER(char*, c, log_malloc(30), log_free);
return 0;
}
这当然需要宏的另一种语法,该语法与原始版本和MSVC扩展版本都不兼容.它还需要一种笨拙的语法来包装和拆分创建函数调用.
理想的解决方案是将所有这些版本(普通的C和各种编译器扩展)协调到一个跨平台的单一语法中.