本文是一篇研究报告,涵盖了编写辅助包装器的一些潜在实现方面,该包装器将自动记录任意 C 函数的参数和结果。这是反射在 C 语言中也可能有用的示例之一。该实现基于 Metac 项目。本文对此进行了介绍。该研究已经取得了一些不错的成果,但仍在进行中。对于如何以更好的方式完成它的评论表示赞赏。
日志记录是调试的重要方式之一。进行正确的日志记录是在不使用调试器的情况下了解可能出现问题的关键。但是打印出每个函数的所有参数及其结果是很烦人的。使用 Metac 的 C 反射可能有能力做到这一点,因为 DWARF 提供的调试信息包含有关每个参数类型的所有数据。一探究竟。这是测试应用程序:
我们想要制作某种包装器来打印 test_function1_with_args 的参数。 Metac 将生成其反射信息,因为 METAC_GSYM_LINK(test_function1_with_args);是在代码中。为了简单起见,选择了 int 和短参数类型。我们如何创建包装器的第一个想法是 - 创建一个宏:
到目前为止,这个包装器仅处理参数,但对于第一步来说是可以的。让我们尝试实现 print_args。这是第一次天真的尝试:
如果我们运行它,我们会看到:
它有效!但它只处理基本类型。我们希望它是通用的。
这里的主要挑战是这一行:
C 的 va_arg 宏要求在编译时知道参数的类型。但是,反射信息仅在运行时提供类型名称。我们能欺骗它吗? va_arg 是一个涵盖内置函数的宏。第二个参数是类型(非常不典型的东西)。但为什么这个东西需要类型呢?答案是 - 了解大小并能够从堆栈中取出它。我们需要覆盖所有可能的大小并获取指向下一个参数的指针。在 Metac 方面,我们知道参数的大小 - 我们可以使用此代码片段来获取它:
作为下一个想法,让我们制作将覆盖 1 个尺寸的宏,并确保我们正确处理它:
通过这种方法,我们涵盖了从 1 到 32 的不同大小。我们可以生成代码并涵盖大小为任意数字的参数,但在大多数情况下,人们使用指针而不是直接传递数组/结构。为了我们的示例,我们将保留 32。
让我们重构我们的函数,使其更可重用,将其分为 2 个 vprint_args 和 print_args,类似于“vprtintf”和 printf:
读者可能会注意到我们添加了 p_tag_map 作为第一个参数。这是为了进一步研究 - 本文中没有使用它。
现在让我们尝试创建一个处理结果的部件。不幸的是,直到 C23 才支持 typeof(gcc 扩展作为一种选项,但它不能与 clang 一起使用),我们遇到了一个困境 - 我们是否要保持 METAC_WRAP_FN 表示法不变,或者可以再传递一个参数- 用作缓冲区的函数结果的类型。也许我们可以使用 libffi 以通用的方式处理这个问题 - Metac 知道类型,但不清楚如何将返回的数据放入适当大小的缓冲区中。为了简单起见,让我们改变我们的宏:
现在我们将 _type_ 作为第一个参数传递来存储结果。如果我们传递不正确的类型或参数 - 编译器会抱怨这个 _type_ res = _fn_(_args_)。这个不错。
打印结果是一项微不足道的任务,我们已经在第一篇文章中做到了。我们还更新我们的测试函数以接受一些不同类型的参数。
这是最终的示例代码。
如果我们运行它,我们会得到评论:
可以看出,Metac 为我们打印了参数和结果的深度表示。一般来说,它是有效的,尽管存在一些缺陷,例如需要单独处理每种大小的参数。
以下是一些额外的限制:
如果您对如何使其更通用有任何建议 - 请发表评论。感谢您的阅读!
以上是C Reflection Magic:使用包装器进行简单记录,用于打印任意函数参数和结果的详细内容。更多信息请关注PHP中文网其他相关文章!