C中的反思支持

adk*_*adk 27 c reflection

我知道它不受支持,但我想知道它周围是否有任何技巧.有小费吗?

Ira*_*ter 19

反射通常是程序分析某些代码结构的手段.此分析用于更改代码的有效行为.

反思作为分析通常很弱; 通常它只能提供对函数和字段名称的访问.这个弱点来自语言实现者本质上不希望在运行时提供完整的源代码,以及适当的分析例程来从源代码中提取所需的内容.

另一种方法是通过使用强大的程序分析工具来解决程序分析,例如,可以完全按照编译器的方式解析源文本.(通常人们会建议滥用编译器本身来执行此操作,但这通常不起作用;编译器机制希望成为编译器,并且很难将其弯曲到其他目的).

我们需要的是一种工具:

  • 解析语言源文本
  • 构建表示程序每个细节的抽象语法树.(如果AST保留源代码布局的注释和其他详细信息,例如列号,文字基数值等,则会很有帮助.)
  • 构建符号表,显示每个标识符的范围和含义
  • 可以从函数中提取控制流
  • 可以从代码中获取数据流
  • 可以为系统构建调用图
  • 可以确定每个指针指向的内容
  • 使用上述事实实现定制分析仪的构建
  • 可以根据这样的自定义分析转换代码(通常通过修改代表解析代码的AST)
  • 可以从修订的AST中重新生成源文本(包括布局和注释).

使用这样的机器,可以在任何需要的细节级别上实现分析,然后转换代码以实现运行时反射将实现的效果.有几个主要的好处:

  • 细节水平或分析量是一个雄心勃勃的问题(例如,它不受运行时反射只能做什么的限制)
  • 没有任何运行时开销来实现行为的反映变化
  • 所涉及的机制可以是通用的,并且可以应用于多种语言,而不仅限于特定语言实现提供的内容.
  • 这与您不支付不使用的C/C++想法兼容.如果您不需要反射,则不需要这种机器.而且你的语言不需要内置弱反射的智力包袱.

请参阅我们的DMS软件重新设计工具包,了解可以为C,Java和COBOL执行上述所有操作的系统,以及大部分用于C++的系统.

  • 这是另一种思考方式.langauges中的反思是关于编译器愿意在目标代码中留下多少源代码来启用反射.除非它保留*all*源代码,否则反射将限制其分析源代码的可用事实的能力. (4认同)

Chr*_*isW 8

围绕它的任何技巧?有小费吗?

编译器可能会选择生成"调试符号文件",调试器可以使用它来帮助调试代码.链接器还可以生成"映射文件".

技巧/提示可能是生成然后读取这些文件.


And*_*ton 5

基于对如何向C++应用程序添加反射的响应(Stack Overflow)以及C++被认为是C的"超集"的事实,我会说你运气不好.

关于为什么C++没有反射(Stack Overflow)也有一个很好的长篇答案.


Nic*_*kis 5

struct我需要在 C++ 项目中的一堆 s 中进行反射。
我创建了一个 xml 文件,其中包含所有这些结构的描述 - 幸运的是,字段类型是原始类型。
我使用模板(不是 C++ template)来自动生成class每个模板struct以及 setter/getter 方法。
在每个中class,我都使用映射来关联字符串名称和类成员(指向成员的指针)。

我并不后悔使用反射,因为它开辟了设计核心功能的新方法,如果没有反射,我什至无法想象。
(顺便说一句,它是使用原始数据库的程序的外部报告生成器)

因此,我使用代码生成、函数指针和映射来模拟反射。


Chr*_*jer 5

我知道以下选项,但所有这些选项都是有代价的并且有很多限制:

  • 使用libdl#include <dfcln.h>
  • objdump调用类似或这样的工具nm
  • 自己解析目标文件(使用相应的库)
  • 涉及解析器并在编译时生成必要的信息。
  • “滥用”链接器来生成符号数组。

下面我将使用一些单元测试框架作为示例,因为单元测试框架的自动测试发现是反射非常方便的典型示例,而这是大多数 C 单元测试框架所缺乏的。

使用libdl( #include <dfcln.h>) (POSIX)

如果您在 POSIX 环境中,可以使用libdl. 插件就是这样开发的。

使用

#include <dfcln.h>
Run Code Online (Sandbox Code Playgroud)

在您的源代码中并链接到-ldl.

然后您可以访问函数dlopen()、 、dlerror()dlsym()dlclose()使用它们在运行时加载和访问/运行共享对象。但是,它并不能让您轻松访问符号表。

这种方法的另一个缺点是,您基本上将反射限制为作为动态库加载的对象(通过在运行时加载的共享对象dlopen())。

跑步nmobjdump

您可以运行nmorobjdump显示符号表并解析输出。对我来说,nm -P --defined-only -g xyz.o给出了很好的结果,并且解析输出是微不足道的。您只对每行的第一个单词(即符号名称)感兴趣,也许对第二个单词(即节类型)感兴趣。

如果您不以某种静态方式知道对象名称,即该对象实际上是一个共享对象,至少在 Linux 上您可能希望跳过以“_”开头的符号名称。

objdumpnm类似的工具通常也可以在 POSIX 环境之外使用。

自己解析目标文件

您可以自己解析目标文件。您可能不想从头开始实现它,而是使用现有的库。nm, ,objdump甚至是这样libdl实现的。您可以查看和的源代码nm以及它们使用的库,以了解它们是如何做的。objdumplibdl

涉及解析器

您可以编写一个解析器和代码生成器,在编译时生成必要的反射信息并将其存储在目标文件中。然后你就有很大的自由,甚至可以实现原始形式的注释。这就是一些单元测试框架(例如AceUnit)所做的事情。

我发现编写一个涵盖直接 C 语法的解析器相当简单。编写一个真正理解 C 并可以处理所有情况的解析器并不是一件容易的事。因此,这有一些限制,具体取决于您想要反思的 C 语法的奇异程度。

“滥用”链接器来生成符号数组

您可以将对要反映的符号的引用放在特殊节中,并使用链接器配置来发出节边界,以便您可以在 C 中访问它们。

我在这里描述了C 中的 N 依赖注入 - 比链接器定义的数组更好的方法?这是如何运作的。

但请注意,这取决于很多因素并且不太便携。我只尝试过GCC/ ld,并且我知道它不适用于所有编译器/链接器。另外,几乎可以保证死代码消除不会检测您如何调用这些东西,因此如果您使用死代码消除,则必须添加所有反射符号作为入口点。

陷阱

对于某些机制,死代码消除可能是一个问题,特别是当您“滥用”链接器来生成符号数组时。可以通过将反射符号作为链接器的入口点来解决这个问题,并且根据符号的数量,这可能既不好也不方便。

结论

结合nmlibdl实际上可以给出相当不错的结果。该组合几乎与 Java 中 JUnit 3.x 使用的反射级别一样强大。给出的反射级别足以实现 C 的 JUnit 3.x 风格的单元测试框架,包括通过命名约定发现测试用例。

涉及解析器需要更多工作,并且仅限于您自己编译的对象,但为您提供了最大的权力和自由。给出的反射级别足以实现 C 的 JUnit 4.x 风格的单元测试框架,包括通过注释发现测试用例。AceUnit是一个 C 单元测试框架,它正是这样做的。

结合解析和链接器来生成符号数组可以给出非常好的结果 - 如果您的环境在您的控制之下,您可以确保以这种方式使用链接器适合您。

当然,您可以结合所有方法将零碎的内容拼接在一起,直到它们满足您的需求。


Her*_*tis 5

提示和技巧始终存在。看看Metaresc库https://github.com/alexanderchuranov/Metaresc

它提供了用于类型声明的接口,该接口还将生成该类型的元数据。基于元数据,您可以轻松地序列化/反序列化任何复杂的对象。开箱即用,您可以序列化/反序列化XML,JSON,XDR,类似Lisp的表示法,C-init表示法。

这是一个简单的示例:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#include "metaresc.h"

TYPEDEF_STRUCT (point_t,
                double x,
                double y
                );

int main (int argc, char * argv[])
{
  point_t point = {
    .x = M_PI,
    .y = M_E,
  };
  char * str = MR_SAVE_XML (point_t, &point);
  if (str)
    {
      printf ("%s\n", str);
      free (str);
    }
  return (EXIT_SUCCESS);
}
Run Code Online (Sandbox Code Playgroud)

该程序将输出

$ ./point
<?xml version="1.0"?>
<point>
  <x>3.1415926535897931</x>
  <y>2.7182818284590451</y>
</point>
Run Code Online (Sandbox Code Playgroud)

库对于最新的gcc和clang正常工作。