2009-08-30 130 views
17

我知道它不被支持,但我想知道是否有任何围绕它的技巧。有小费吗?反射支持C

+0

我真的怀疑它。 – ChaosPandion 2009-08-30 03:48:14

+4

如果你想反思,C和C++对你来说是错误的语言。这违背了他们的理念:“你不支付你不使用的东西”。 – Crashworks 2009-08-30 04:02:51

+0

您可以通过使用C/C++语言之外的机制来获得反射的效果。查看其他答案。 – 2009-08-30 04:59:39

回答

14

反思一般是程序分析一些代码的结构的手段。 此分析用于更改代码的有效行为。

反思作为分析一般非常薄弱;通常它只能提供对函数和字段名称的访问。这种弱点来自于语言实现者,他们基本上不希望在运行时使完整的源代码可用,以及适当的分析例程以从源代码中提取想要的东西。

另一种方法是通过使用强大的程序分析工具,例如可以完全按照编译器执行的方式解析源文本的方法来处理程序分析。 (通常人们建议滥用编译器本身来做到这一点,但通常这是行不通的;编译器机器想成为一个编译器,并且很难将其用于其他目的)。

需要的是一个工具:

  • 解析语言的源文本
  • 构建代表程序的每一个细节抽象语法树。 (这是有益的,如果AST的保留意见和源代码 布局的其他细节,如列数,文字基数值等)
  • 构建表示每个标识符的范围和含义的符号表
  • 可以提取控制从功能
  • 可以extact从代码
  • 数据流可以构造调用图的系统流
  • 可以确定哪些每个指针指向的
  • 启用自定义分析仪使用上述F上的结构动作
  • 可根据这样的自定义的转换的代码分析 (通常通过修改表示所解析的代码的AST)从
  • 可以再生源文本(包括布局和注释)修订后的AST。

使用这种机制,可以在需要的任何细节层次上实现分析,然后转换代码以实现运行时反射将实现的效果。 有几大好处:

  • 详细度或分析的量的野心物质(例如,它不 由什么运行时反射只能做有限的)
  • 没有任何运行时开销以实现反映的行为变化
  • 涉及的机器可以是通用的并且可以应用于多种语言,而不仅限于特定语言实现提供的内容。
  • 这与C/C++的想法是兼容的,你不支付你不用的东西。 如果你不需要反射,你不需要这个机器。和你的语言 并不需要有弱反射内置的智能行李。

请参见我们DMS Software Reengineering Toolkit的系统,可以做到以上的为C,Java和COBOL,而且大部分为C++。

+0

如果您没有源代码,该怎么办? – Crashworks 2009-08-30 23:05:11

+0

如果您没有源代码,您最好希望能够在您的编程系统中获得解答您具体问题的思考。 OP的问题是关于C,它根本没有内置任何反射。如果你有一个反射系统,比如Java,你经常会发现它不会给你足够的细节; Java是否提供了OP请求的函数参数的名称和类型?关键是你需要一个强大的系统来分析代码,而大多数编程系统/语言不会提供。因此,走出去并获得一个* *可以*的工具,无一例外。 – 2009-08-30 23:34:23

+3

这是另一种思考方式。在语言中的反射是关于编译器愿意在对象代码中保留多少源代码来启用反射。除非它保留所有的源代码,否则反思将会限制它分析关于源代码的可用事实的能力。 – 2009-08-31 00:29:46

2

你需要从头开始实施它。在C语言中,结构和复合类型中没有任何运行时信息。元数据根本不存在于标准中。

+0

或者,如果您发现自己实施全反射系统有点令人生畏,那么您可以使用旨在分析/转换具有所有必要机器的代码的工具。看到别人对这个问题的答案。 – 2009-09-02 16:32:01

2
  1. 为C实现反射会简单得多...因为C是简单的语言。
  2. 确定程序有一些基本选项,例如通过调用dlopen/dlsym来检测函数是否存在 - 取决于您的需求。
  3. 有一些工具用于创建可使用tcc修改/扩展自己的代码。
  4. 您可以使用上述工具来创建您自己的代码分析器。
+0

你可以找到一个函数的参数,说罪恶使用dlopen/dlsym?什么是一些好的代码分析器? – adk 2009-08-30 05:08:36

+1

“你能找到函数的参数吗”不,只是存在这样的符号。 – Artyom 2009-08-30 06:20:00

7

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

编译器可能会有选择地生成'调试符号文件',调试器可以用它来帮助调试代码。链接器也可以生成一个'映射文件'。

一个技巧/提示可能是生成并读取这些文件。

3

我需要在C++项目中对一堆struct进行反射。
我用所有这些结构的描述创建了一个xml文件 - 幸运的是这些字段类型是原始类型。
我用模板(未C++ template)自动生成每个struct与设定器/吸气剂的方法沿class
在每个class我用一个映射关联字符串名称和类成员(指向成员)。

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

所以,我使用的代码生成,函数指针和映射到模拟反射。

+0

因此,您使用特设方法来实现愿望效果。如果这对你有用,太好了。一种结构化的方法可以让你跳过这些结构的手工编码的XML描述,并且“反映”结构声明本身的实际数据。请参阅此问题的其他解答。 – 2009-09-02 16:31:06

+0

正是我想到的,将写一个PHP脚本分析我的.cpp文件,然后转储C++代码,这反映了。我需要它通过脚本引擎来使用C结构。 – lama12345 2016-11-19 11:31:25

1

我知道下面的选项,但所有付出的成本和很大的局限性:

  • 使用libdl#include <dfcln.h>
  • 呼叫像objdumpnm
  • 一种工具解析对象文件(使用相应的库)
  • 包含解析器并在编译时生成必要的信息。
  • “滥用”链接器来生成符号数组。

我将使用一些单元测试框架作为示例,因为单元测试框架的自动测试发现是反射非常方便的一个典型示例,并且它是大多数单元测试框架的C语言不足。

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

如果你在一个POSIX环境,反射的一点点可以使用libdl完成。插件是以这种方式开发的。

使用

#include <dfcln.h> 

在你的源代码,然后链接-ldl

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

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

运行nmobjdump

您可以运行nmobjdump显示符号表和解析输出。 对我来说,nm -P --defined-only -g xyz.o给出了很好的结果,解析输出很简单。 您只会对每行的第一个单词感兴趣,这是符号名称,也可能是第二个单词,即单元类型。

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

objdump,nm或类似的工具通常也可以在POSIX环境之外获得。

自己解析目标文件

你可以自己解析目标文件。你可能不想从头开始实现,但为此使用现有的库。这是如何实现nm,objdump乃至libdl。您可以查看nmobjdumplibdl的源代码以及他们使用的库,以了解他们如何执行他们的工作。

涉及一个分析器

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

我发现写一个包含直接C语法的解析器是相当简单的。编写一个真正理解C并能处理所有情况的解析器并不是微不足道的。 因此,这有一些限制,取决于您想要反思的C语法多么奇特。

“滥用”链接器生成符号列

你可以把要反映的符号引用时在一个特殊的部分,并使用链接器的配置发出的区间边界,所以你可以在C访问它们。

我在这里N-Dependency injection in C - better way than linker-defined arrays?描述了这一过程。

但要小心,这取决于很多事情,不是很便携。我只用GCC/ld试过,我知道它不适用于所有编译器/链接器。此外,它几乎可以确保死代码消除不会检测到你如何调用这个东西,所以如果你使用死代码消除,你将不得不添加所有反射的符号作为入口点。

陷阱

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

结论

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

涉及解析器是你自己编译的对象更多的工作和有限的,但给你最权力和自由。给定的反射级别足以为C实现JUnit 4.x风格的单元测试框架,包括通过注释进行测试用例发现。 AceUnit是C的单元测试框架,完全是这样。

结合分析,并生成符号阵列可以产生非常好的结果链接 - 如果你的环境是你的控制之下这么多,你可以确保与连接器的工作这种方式对你的作品。

当然,你可以将所有的方法,直到他们满足您的需求来的点点滴滴缝合在一起。

2

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

它提供了类型声明的接口,它也会为类型生成元数据。基于元数据,您可以轻松地序列化/反序列化任何复杂的对象。开箱即用,您可以序列化/反序列化XML,JSON,XDR,Lisp-like符号,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); 
} 

这一计划将输出

$ ./point 
<?xml version="1.0"?> 
<point> 
    <x>3.1415926535897931</x> 
    <y>2.7182818284590451</y> 
</point> 

库工作正常,最新的gcc和铿锵。

0

只要制作任何类型的键控表,树,链表或任何你喜欢管理或发现有效的集合。添加一个键是否字符串,类型/ ID组合或什么都不指定,并将地址指向函数或结构体。反思的一个超级幼稚的版本可以是以下的集合:

struct reflectable{ 
    size_t size,id,type; // describes payload 
    char* name; 
    void* payload; 
} 

有了一个大醇”开关情况下你为每个类型或名称或宏做附上相同的处理程序。另外总是附加函数,这些函数是可反射结构中的任何东西的接收者,它们与处理程序相同,但是更多的调度模型直接位于容器中。