我正在尝试内存管理并尝试创建一些可以以任何方式帮助它的东西。
一种好的方法是只有一个return
在任何函数中。可能标有标签(是的,所以可以goto
它,但这也经常被劝阻)。当然:一定要知道谁拥有分配的内存以及所有权何时(以及何处)转移!
现在,让我们...
[..] 在 C 中重复 Go 的“延迟”功能。
首先,为了推迟调用,我们需要存储函数(指向它的指针)以及计算的参数。由于 C 是静态类型的,我们需要将其统一为单一类型:
struct Fn {
void * parameters; // pointer to memory where the parameters are stored
void (*function)(void *); // pointer to function able to unpack parameters from above
struct Fn * next; // we want a stack, so ...
};
对于我们最终要推迟的每个函数,我们需要一种方法来存储它的参数。所以我们定义一个struct
能够保存参数以及能够从中解压参数的函数struct
:
#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)); \
}
The N
是参数的数量。有一些技巧可以从__VA_ARGS__
,但我将把它作为练习留给读者。该宏包含另外两个宏扩展,PARAMS
and CPARAMS
。前者扩展为适合定义的列表struct
内容。后者扩展为代码以提取struct
成员作为参数:
#define PARAM_0(...)
#define PARAM_1(type, ...) type p1; PARAM_0(__VA_ARGS__)
#define PARAM_2(type, ...) type p2; PARAM_1(__VA_ARGS__)
#define PARAM_3(type, ...) type p3; PARAM_2(__VA_ARGS__)
#define PARAM_4(type, ...) type p4; PARAM_3(__VA_ARGS__)
#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
, using 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
:
#define SPARAM_0(...)
#define SPARAM_1(value, ...) parameters->p1 = (value); SPARAM_0(__VA_ARGS__)
#define SPARAM_2(value, ...) parameters->p2 = (value); SPARAM_1(__VA_ARGS__)
#define SPARAM_3(value, ...) parameters->p3 = (value); SPARAM_2(__VA_ARGS__)
#define SPARAM_4(value, ...) parameters->p4 = (value); SPARAM_3(__VA_ARGS__)
#define SPARAMS(N, ...) SPLICE(SPARAM_, N)(__VA_ARGS__)
注意:这通过使参数从最后到第一个进行评估来固定参数评估的顺序。 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);
}
}
一个小测试 http://ideone.com/fNKJU1:
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,一部分库函数调用 https://www.gnu.org/software/libffcall/.
现在,我真的需要我的括号......lots其中 (())))(()(((()