我正在尝试进行内存管理实验,并尝试创建任何形式的有助于此的东西。
一个好的方法是在任何函数中仅具有一个return。可能会标记为一个标签(是的,可以使用goto,但这通常也不被鼓励)。当然:始终确保知道谁拥有分配的内存以及何时(以及在哪里)所有权已转移!
现在,让我们……重复Go中的“defer”功能,在C中实现它。
首先,为了推迟调用,我们需要存储函数(指向它的指针)以及评估的参数。由于C是静态类型的,我们需要将其统一为单个类型:
struct Fn {
void * parameters;
void (*function)(void *);
struct Fn * next;
};
对于我们最终要推迟的每个函数,我们需要一种存储它的参数的方式。因此,我们定义了一个能够容纳参数的结构体和一个能够从该结构体中解包参数的函数:
#define MAKE_DEFERRABLE(name, N, ...) \
struct deferred_ ## name ## _parameters { PARAMS(N, __VA_ARGS__) }; \
void deferred_ ## name (void * p) { \
struct deferred_ ## name ## _parameters * parameters = p; \
printf(" -- Calling deferred " #name "\n"); \
(void)name(CPARAMS(N)); \
}
N
是参数的数量。有一些技巧可以从__VA_ARGS__
中推断出来,但我会把它留给读者作为练习。该宏包含另外两个宏扩展,PARAMS
和CPARAMS
。前者扩展成适合定义struct
内容的列表。后者扩展成提取struct
成员作为参数的代码:
#define PARAM_0(...)
#define PARAM_1(type, ...) type p1
#define PARAM_2(type, ...) type p2
#define PARAM_3(type, ...) type p3
#define PARAM_4(type, ...) type p4
#define PARAMS(N, ...) SPLICE(PARAM_, N)(__VA_ARGS__)
#define CPARAM_0
#define CPARAM_1 parameters->p1
#define CPARAM_2 parameters->p2, CPARAM_1
#define CPARAM_3 parameters->p3, CPARAM_2
#define CPARAM_4 parameters->p4, CPARAM_3
#define CPARAMS(N) SPLICE(CPARAM_, N)
如果我们想要推迟具有超过4个参数的函数,则需要进行调整。
SPLICE
是一个不错的小助手:
#define SPLICE_2(l,r) l##r
#define SPLICE_1(l,r) SPLICE_2(l,r)
#define SPLICE(l,r) SPLICE_1(l,r)
接下来,我们需要以某种方式存储延迟函数。为了简单起见,我选择动态分配它们并保持对最近一个的全局指针:
struct Fn * deferred_fns = NULL
显然,您可以在许多方面进行扩展:使用(有限的)静态存储、使其成为线程本地的、使用每个函数的
deferred_fns
、使用
alloca
等。
但是这里提供的是简单的、不适用于生产环境的版本(缺少错误检查)。
#define DEFER(name, N, ...) \
do { \
printf(" -- Deferring a call to " #name "\n"); \
if (deferred_fns == NULL) { \
deferred_fns = malloc(sizeof(*deferred_fns)); \
deferred_fns->next = NULL; \
} else { \
struct Fn * f = malloc(sizeof(*f)); \
f->next = deferred_fns; \
deferred_fns = f; \
} \
deferred_fns->function = &(deferred_ ## name); \
struct deferred_ ## name ##_parameters * parameters = malloc(sizeof(*parameters)); \
SPARAMS(N,__VA_ARGS__); \
deferred_fns->parameters = parameters; \
} while(0)
这段代码创建了一个新的
struct Fn
,将其置于栈顶(以单向链表
deferred_fns
的形式),并相应地设置其成员。重要的
SPARAMS
将参数保存到相应的
struct
中:
注意:通过让参数从后往前进行评估,可以解决参数评估顺序的问题。C语言并没有强制规定评估顺序。
最后,还有一种方便的方法来运行这些延迟函数。
void run_deferred_fns(void) {
while (deferred_fns != NULL) {
deferred_fns->function(deferred_fns->parameters);
free(deferred_fns->parameters);
struct Fn * bye = deferred_fns;
deferred_fns = deferred_fns->next;
free(bye);
}
}
一个小测试
void foo(int x) {
printf("foo: %d\n", x);
}
void bar(void) {
puts("bar");
}
void baz(int x, double y) {
printf("baz: %d %f\n", x, y);
}
MAKE_DEFERRABLE(foo, 1, int);
MAKE_DEFERRABLE(bar, 0);
MAKE_DEFERRABLE(baz, 2, int, double);
int main(void) {
DEFER(foo, 1, 42);
DEFER(bar, 0);
DEFER(foo, 1, 21);
DEFER(baz, 2, 42, 3.14);
run_deferred_fns();
return 0;
}
为了实现与您示例中相同的行为,将
deferred_fns
设为本地变量,并将其作为参数传递给
run_deferred_fns
。使用简单的宏进行包装,完成:
#define PREPARE_DEFERRED_FNS struct Fn * deferred_fns = NULL;
#define RETURN(x) do { run_deferred_fns(deferred_fns); return (x); } while (0)
欢迎来到疯狂世界。
注:我的解决方案在“源代码级别”上运行。这意味着您需要在源代码中指定可延迟的函数。这意味着您不能通过dlopen加载的函数进行延迟。如果您愿意,还有一种不同的方法,即在ABI级别上工作:avcall,它是
libffcall的一部分。
现在,我真的需要我的括号...很多括号(())))(()(((()
atexit
еҮҪж•°пјҹжҲ–иҖ…constructor
е’Ңdestructor
еҮҪж•°еұһжҖ§з”ЁдәҺmain
еҮҪж•°пјҹ - David C. Rankin