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

2024-04-23

我正在尝试内存管理并尝试创建一些可以以任何方式帮助它的东西。现在我正在思考是否有任何方法可以在 C 中重复 Go 的“延迟”功能。

对于那些不知道延迟是什么的人来说,这是一个简单的例子:

package main

import "fmt"

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

将打印

3
2
1

所以我正在考虑一些宏,这些宏会将带有参数的函数推送到某个堆栈,并在调用函数退出时调用它们。像这样的事情:

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

有没有办法(当然,除了制作我自己的预处理器),将带有参数的函数调用存储在某处?换句话说,我希望之前的代码能够像这样进行预处理:

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 trick.


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

一种好的方法是只有一个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其中 (())))(()(((()

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

有没有办法保存带有参数的函数调用? 的相关文章

  • 将复选框添加到 UniformGrid

    我正在尝试将复选框动态添加到 wpf 中的统一网格中 但看起来网格没有为它们分配足够的空间 所以它们都有点互相重叠 这就是我将它们添加到后面的代码中的方法 foreach string folder in subfolders PathCh
  • 如何使用GDB修改内存内容?

    我知道我们可以使用几个命令来访问和读取内存 例如 print p x 但是如何更改任何特定位置的内存内容 在 GDB 中调试时 最简单的是设置程序变量 参见GDB 分配 http sourceware org gdb current onl
  • pthread_cond_timedwait() 和 pthread_cond_broadcast() 解释

    因此 我在堆栈溢出和其他资源上进行了大量搜索 但我无法理解有关上述函数的一些内容 具体来说 1 当pthread cond timedwait 因为定时器值用完而返回时 它如何自动重新获取互斥锁 互斥锁可能被锁定在其他地方 例如 在生产者
  • 如何避免情绪低落?

    我有一个实现状态模式每个状态处理从事件队列获取的事件 根据State因此类有一个纯虚方法void handleEvent const Event 事件继承基础Event类 但每个事件都包含其可以是不同类型的数据 例如 int string
  • C++ 子字符串返回错误结果

    我有这个字符串 std string date 20121020 我正在做 std cout lt lt Date lt lt date lt lt n std cout lt lt Year lt lt date substr 0 4 l
  • 使闭包捕获的变量变得易失性

    闭包捕获的变量如何与不同线程交互 在下面的示例代码中 我想将totalEvents 声明为易失性的 但C 不允许这样做 是的 我知道这是错误的代码 这只是一个例子 private void WaitFor10Events volatile
  • 实时服务器上的 woff 字体 MIME 类型错误

    我有一个 asp net MVC 4 网站 我在其中使用 woff 字体 在 VS IIS 上运行时一切正常 然而 当我将 pate 上传到 1and1 托管 实时服务器 时 我得到以下信息 网络错误 404 未找到 http www co
  • Newtonsoft JSON PreserveReferences处理自定义等于用法

    我目前在使用 Newtonsoft Json 时遇到一些问题 我想要的很简单 将要序列化的对象与所有属性和子属性进行比较以确保相等 我现在尝试创建自己的 EqualityComparer 但它仅与父对象的属性进行比较 另外 我尝试编写自己的
  • 当 contains() 工作正常时,xpath 函数ends-with() 工作时出现问题

    我正在尝试获取具有以特定 id 结尾的属性的标签 like span 我想获取 id 以 国家 地区 结尾的跨度我尝试以下xpath span ends with id Country 但我得到以下异常 需要命名空间管理器或 XsltCon
  • 为什么#pragma optimize("", off)

    我正在审查一个 C MFC 项目 在某些文件的开头有这样一行 pragma optimize off 我知道这会关闭所有以下功能的优化 但这样做的动机通常是什么 我专门使用它来在一组特定代码中获得更好的调试信息 并在优化的情况下编译应用程序
  • 如何将图像和 POST 数据上传到 Azure 移动服务 ApiController 终结点?

    我正在尝试上传图片and POST表单数据 尽管理想情况下我希望它是json 到我的端点Azure 移动服务应用 我有ApiController method HttpPost Route api upload databaseId sea
  • 在数据库中搜索时忽略空文本框

    此代码能够搜索数据并将其加载到DataGridView基于搜索表单文本框中提供的值 如果我将任何文本框留空 则不会有搜索结果 因为 SQL 查询是用 AND 组合的 如何在搜索 从 SQL 查询或 C 代码 时忽略空文本框 private
  • 从路径中获取文件夹名称

    我有一些路c server folderName1 another name something another folder 我如何从那里提取最后一个文件夹名称 我尝试了几件事 但没有成功 我只是不想寻找最后的 然后就去休息了 Thank
  • 如何衡量两个字符串之间的相似度? [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 给定两个字符串text1 and text2 public SOMEUSABLERETURNTYPE Compare string t
  • C++ 复制初始化和直接初始化,奇怪的情况

    在继续阅读本文之前 请阅读在 C 中 复制初始化和直接初始化之间有区别吗 https stackoverflow com questions 1051379 is there a difference in c between copy i
  • 插入记录后如何从SQL Server获取Identity值

    我在数据库中添加一条记录identity价值 我想在插入后获取身份值 我不想通过存储过程来做到这一点 这是我的代码 SQLString INSERT INTO myTable SQLString Cal1 Cal2 Cal3 Cal4 SQ
  • 在 Dynamics CRM 插件中访问电子邮件发件人地址

    我正在编写一个 Dynamics CRM 2011 插件 该插件挂钩到电子邮件实体的更新后事件 阶段 40 pipeline http msdn microsoft com en us library gg327941 aspx 并且在此阶
  • C - 直接从键盘缓冲区读取

    这是C语言中的一个问题 如何直接读取键盘缓冲区中的数据 我想直接访问数据并将其存储在变量中 变量应该是什么数据类型 我需要它用于我们研究所目前正在开发的操作系统 它被称为 ICS OS 我不太清楚具体细节 它在 x86 32 位机器上运行
  • 为什么我收到“找不到编译动态表达式所需的一种或多种类型。”?

    我有一个已更新的项目 NET 3 5 MVC v2 到 NET 4 0 MVC v3 当我尝试使用或设置时编译出现错误 ViewBag Title财产 找不到编译动态表达式所需的一种或多种类型 您是否缺少对 Microsoft CSharp
  • const、span 和迭代器的问题

    我尝试编写一个按索引迭代容器的迭代器 AIt and a const It两者都允许更改容器的内容 AConst it and a const Const it两者都禁止更改容器的内容 之后 我尝试写一个span

随机推荐