有没有办法用参数保存函数调用?

我正在尝试内存管理并尝试创建一些能够以任何方式帮助它的东西.现在我正在考虑是否有任何方法可以重复来自C的Go的’延迟’功能.

那些不知道延迟是什么的人的快速例子:

package main

import "fmt"

func main() {
    defer fmt.Println("1")
    defer fmt.Println("2")
    defer fmt.Println("3")
    return
}

将打印

3
2
1

所以我正在考虑一些宏将函数与params推送到某个堆栈,并在调用函数exit时调用它们.像这样的东西:

int func(void)
{
    MEMSTACK_INIT;

    char * string = NULL;
    node_t * node = NULL;
    MEMSTACK_PUSH(free(string));
    MEMSTACK_PUSH(NodeFree(&node));

    <..>

    switch (something)
    {
    case ONE : RETURN ERROR_ONE;
    case TWO : RETURN ERROR_TWO;
    case THR :
        switch (something else)
        {
            <.. Many more code ..>
        }
    }        

    RETURN ERROR_GOOD;
}

有没有办法(当然除了制作我自己的预处理器),用params存储一个函数调用?换句话说,我希望以下代码预处理前面的代码:

int func(void)
{
    <.. Some MEMSTACK initialisation stuff (if needed) ..>

    char * string = NULL;
    node_t * node = NULL;

    <..>

    switch (something)
    {
    case ONE :             
        free(string);
        NodeFree(&node);
        return ERROR_ONE;
    case TWO :             
        free(string);
        NodeFree(&node);
        return ERROR_TWO;
    case THR :
        switch (something else)
        {
            <.. Many more code ..>
        }
    }        

    free(string);
    NodeFree(&node);
    return ERROR_GOOD;
}

对于在退出之前需要大量清理的功能来说,这将是一件好事.
是的,是的,我知道goto清理技巧.

最佳答案

I’m experimenting with memory management and trying to create something that will help with it in any way.

一个好的方法是在任何函数中只返回一个.可能标有标签(是的,所以可以gotoit,但这也经常被劝阻).当然:始终确定知道谁拥有已分配的内存以及何时(以及在何处)转让所有权!

现在,让我们……

[..] repeat the ‘defer’ functionality from Go in C.

首先,为了推迟调用,我们需要存储函数(指向它的指针)以及计算的参数.由于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 ...
};

对于我们最终将推迟的每个函数,我们需要一种方法来存储它的参数.所以我们定义了一个能够保存参数的结构和一个能够从该结构中解包参数的函数:

#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成员提取为参数:

#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,使用alloca,…

…但这里是简单的,而不是生产就绪(MISSING ERROR CHECKS)变体:

#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)

这只是分配一个新的结构Fn,使其成为堆栈的顶部(读取单链接列表deferred_fns)并相应地设置其成员.重要的SPARAMS将参数保存到相应的结构中:

#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);
  }
}

A small test

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的一部分.

现在,我真的需要我的括号……很多(())))(()(((()

点赞