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

时间:2016-06-02 13:54:18

标签: c

我正在尝试内存管理并试图以任何方式创建有助于它的东西。现在我正在考虑是否有任何方法可以重复来自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 cleanup技巧。

1 个答案:

答案 0 :(得分:2)

  

我正在尝试内存管理并尝试创建一些能够以任何方式帮助它的东西。

一个好的方法是在任何函数中只有一个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)); \
  }

N是参数的数量。有一些技巧可以从__VA_ARGS__推断出来,但我会将其作为读者的练习。该宏包含两个其他宏扩展,PARAMSCPARAMS。前者扩展为适合定义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,使用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)

这只是分配一个新的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);
  }
}

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

现在,我真的需要我的括号...... 很多他们(())))(()(((()