为什么C没有反射?

这是一个有点离奇的问题 . 我的目标是理解语言设计决策并确定C中反思的可能性 .

  • 为什么C语言委员会没有采用语言实现反思?对于不在虚拟机上运行的语言(如java),反射是否太难?

  • 如果要实施C的反思,那么挑战是什么?

我想反射的使用是众所周知的:编辑器可以更容易编写,程序代码更小,可以为单元测试生成模拟等等 . 但是,如果你也可以对反射的使用发表评论,那将会很棒 .

回答(14)

3 years ago

C中的反射有几个问题 .

  • 它's a lot of work to add, and the C++ committee is fairly conservative, and don' t花时间在激进的新功能上,除非它们得到回报 . (有关添加类似于.NET程序集的模块系统的建议已经完成,虽然我认为's general consensus that it'很好,但目前它并不是它们的首要任务,并且一直被推迟到C 0x之后 . 这个功能的动机是摆脱 #include 系统,但它也会启用至少一些元数据) .

  • 您不支付您不使用的费用 . 这是C背后必须的基本设计理念之一 . 如果我可能永远不需要,我的代码为什么要携带元数据?此外,元数据的添加可能会阻止编译器进行优化 . 如果我可能永远不需要那些元数据,为什么我要在代码中支付这笔费用?

  • 这引出了另一个重点:C对编译代码的保证很少 . 只要产生的功能符合预期,编译器就可以做任何喜欢的事情 . 例如,您的课程不需要实际存在 . 编译器可以优化它们,内联它们所做的一切,并且它经常这样做,因为即使是简单的模板代码也往往会创建相当多的模板实例 . C标准库依赖于这种积极的优化 . 如果可以优化实例化和破坏对象的开销,则函数仅具有性能 . 向量上的 operator[] 仅与性能中的原始数组索引相当,因为整个运算符可以内联并因此完全从已编译的代码中删除 . C#和Java对编译器的输出提供了很多保证 . 如果我在C#中定义一个类,那么该类将存在于生成的程序集中 . 即使我从不使用它 . 即使可以内联所有对其成员函数的调用 . class 必须在那里,以便反思可以找到它 . 部分结果是通过C#编译为字节码来缓解,这意味着JIT编译器可以删除类定义和内联函数(如果它喜欢),即使初始C#编译器可以期望看到它定义的每个类,这意味着编译器必须保留所有已定义的类,即使它们不是必需的 .

  • 然后有模板 . C中的模板与其他语言中的泛型不同 . 每个模板实例化都会创建一个新类型 . std::vector<int> 是一个完全独立的类 std::vector<float> . 这在整个程序中增加了许多不同的类型 . 我们的反思应该看到什么?模板 std::vector ?但是怎么可能呢,因为's a source-code construct, which has no meaning at runtime? It' d必须看到单独的类 std::vector<int>std::vector<float> . 和 std::vector<int>::iteratorstd::vector<float>::iteratorconst_iterator 相同,依此类推 . 一旦您进入模板元编程,您很快就会实例化数百个模板,所有这些模板都会被编译器内联并再次删除 . 它们没有任何意义,除非作为编译时元程序的一部分 . 是否所有这几百个课程都可以反映出来?他们甚至可以保证我定义的类实际上就在那里 . 另一个问题是模板类在实例化之前不存在 . 想象一个使用 std::vector<int> 的程序 . 我们的反射系统应该能够看到 std::vector<int>::iterator 吗?一方面,你'd certainly expect so. It'是一个重要的类,它是根据 std::vector<int> 定义的,它确实存在于元数据中 . 另一方面,如果程序实际上从未使用过这个迭代器类模板,那么它的类型将永远不会被实例化,因此编译器在运行时创建它的时间太晚了,因为它需要访问源代码 .

  • 最后,反射并不能解决所有问题,但是在很多情况下,你可以编写一个在编译时做同样事情的元程序 . boost::type_traits 是一个简单的例子 . 你想知道类型 T ?检查它的 type_traits . 在C#中,您很容易替换,用于自动生成的序列化代码),但它会为C带来一些重要的成本,并且它不像其他语言那样频繁 .

编辑:回应评论:

cdleary:是的,调试符号做类似的事情,因为它们存储有关的元数据可执行文件中使用的类型 . 但他们也遇到了我所描述的问题 . 如果你曾尝试调试发布版本,你就会明白我的意思 . 在源代码中创建了一个类,存在很大的逻辑空白,它已经在最终代码中被内联了 . 如果您使用反射来做任何有用的事情,那么您需要它更可靠和一致 . 事实上,几乎每次编译时类型都会消失和消失 . 您更改了一小部分细节,编译器决定更改哪些类型内联,哪些类型不作为响应 . 当您甚至不能保证在元数据中表示最相关的类型时,如何从中提取任何有用的内容?您正在寻找的类型可能已经在最后一个版本中存在,但现在它已经消失了 . 而且明天,有人会在一个小小的无辜功能中进行一次无辜的小改变,这使得这种类型变得足够大,以至于它不会被完全内联,所以它会再次回归 . 这对于调试符号仍然有用,但不仅如此 . 我讨厌在这些条件下为类生成序列化代码 .

Evan Teran:当然这些问题可以解决 . 但这又回到了我的观点#1 . 它'd take a lot of work, and the C++ committee has plenty of things they feel is more important. Is the benefit of getting some limited reflection (and it would be limited) in C++ really big enough to justify focusing on that at the expense of other features? Is there really a huge benefit in adding features the core language which can already (mostly) be done through libraries and preprocessors like QT'?或许,但是,例如,如果这样的库没有能够在标准库上使用反射,那么需求就不那么紧迫了 . 什么样的反思不会让你看到 std::vector ?模板是C的重要组成部分 . 对模板不起作用的功能基本上没用 .

但是你这是语言的一个重大变化 . 就像现在一样,类型只是一个编译时构造 . 它们的存在是为了编译器的利益,而不是其他任何东西 . 编译完代码后,就没有类 . 如果你自己伸展,你可能会认为函数仍然存在,但实际上,所有的都是一堆跳转汇编程序指令,并且在添加这样的元数据时,很多堆栈push / pop _442079都没有多少 .

但就像我说的那样,有一个改进编译模型的建议,添加自包含的模块,存储选择类型的元数据,允许其他模块引用它们而不必乱用 #include . 那个标准委员会感到惊讶的是,并没有因为太大的变化而抛出提案 . 也许在5到10年内? :)

3 years ago

反射需要一些关于类型的元数据存储在可以查询的地方 . 由于C编译为本机机器代码并且由于优化而经历了大量更改,因此在编译过程中应用程序的高级视图几乎丢失,因此,无法在运行时查询它们 . Java和.NET在虚拟机的二进制代码中使用非常高级别的表示,使得这种反射水平成为可能 . 然而,在一些C实现中,存在称为运行时类型信息(RTTI)的东西,其可被视为反射的精简版本 .

3 years ago

反射可以是可选的,就像预处理器指令一样 . 就像是

#pragma enable reflection

这样我们可以充分利用这两个世界,没有反射就可以创建这个pragma库(没有讨论任何开销),那么无论他们想要速度还是易用性都会影响个体开发人员 .

3 years ago

如果C可能有:

  • 变量名,变量类型和 const 修饰符的类成员数据

  • 一个函数参数迭代器(只有位置而不是名称)
    函数名称,返回类型和 const 修饰符的

  • 类成员数据

  • 父类列表(与定义的顺序相同)

  • 模板成员和父类的数据;扩展模板(意味着实际类型可用于反射API,而不是'template information of how to get there')

这足以在无类型数据处理的关键时刻创建非常易于使用的库,这在当今的Web和数据库应用程序(所有的orms,消息传递机制,xml / json解析器,数据序列化等)中非常普遍 .

例如, Q_PROPERTY 宏(Qt框架的一部分)http://qt.nokia.com/doc/4.5/properties.html支持的基本信息扩展到涵盖类方法和e) - 对C和软件社区来说是非常有益的 .

当然,我所指的反映不会涵盖语义或更复杂的问题(如评论源代码行号,数据流分析等) - 但我认为这些都不需要成为语言标准的一部分 .

3 years ago

如果将C用作数据库访问,Web会话处理/ http和GUI开发的语言,我认为C中的反思至关重要 . 缺乏反射会阻止ORM(如Hibernate或LINQ),XML和JSON解析器实现类,数据序列化和许多其他方面(其中)最初无类型数据必须用于创建类的实例 .

在构建过程中可供软件开发人员使用的编译时开关可用于消除“您为所使用的内容付费”的问题 .

我的固件开发人员不需要反射来从串口读取数据 - 然后很好不要使用交换机 . 但作为一个想要继续使用C的数据库开发人员,我不断地分阶段使用可怕的,难以维护的代码,在数据成员和数据库构造之间映射数据 .

Boost序列化和其他机制都没有真正解决反射 - 它必须由编译器完成 - 一旦完成,C将再次在学校中进行并用于处理数据处理的软件中

对我来说这个问题#1(和naitive线程原语是问题#2) .

3 years ago

这基本上是因为它是“可选的额外” . 许多人选择C而不是像Java和C#这样的语言,这样他们就可以更好地控制编译器输出,例如:一个更小,和/或更快的程序 .

如果您选择添加反射,则有various solutions available .

3 years ago

所有语言都不应该尝试包含所有其他语言的所有功能 .

C本质上是一个非常非常复杂的宏汇编程序 . 它不是(传统意义上的)高级语言,如C#,Java,Objective-C,Smalltalk等 .

为不同的工作提供不同的工具是很好的 . 如果我们只有锤子,那么所有东西都会看起来像指甲等 . 有了脚本语言对某些工作很有用,反射的OO语言(Java,Obj-C,C#)对另一类工作很有用,而且超级 - 高效的裸机接近机器语言对于另一类作业(C,C,汇编程序)非常有用 .

C在将Assembler技术扩展到令人难以置信的复杂度管理水平方面做得非常出色,而且抽象使得编程更大,更复杂的任务对人类来说更加可能 . 但对于那些从严格的高级角度(Lisp,Smalltalk,Java,C#)处理问题的人来说,它不一定是最适合的语言 . 如果您需要具有这些功能的语言来最好地实现问题的解决方案,那么感谢那些为我们所有人创建这些语言的人!

但C适用于那些因任何原因需要在代码和底层机器操作之间 Build 强关联的人 . 无论是效率,编程设备驱动程序,还是与低级OS服务或其他任何内容的交互,C都更适合这些任务 .

C#,Java,Objective-C都需要更大,更丰富的运行时系统来支持它们的执行 . 该运行时必须交付给相关系统 - 预先安装以支持软件的运行 . 并且必须为各种目标系统维护该层,由其他语言定制,以使其在该平台上运行 . 而那个中间层 - 主机操作系统和你的代码之间的自适应层 - 运行时,几乎总是用C或C这样的语言编写,效率是#1,可以预见地理解软件和硬件之间的确切交互可以很好理解,并操纵到最大的收益 .

我喜欢Smalltalk,Objective-C,并且拥有丰富的运行时系统,包括反射,元数据,垃圾收集等 . 可以编写出色的代码来利用这些功能!但这只是堆栈上的一个更高层,一层必须停留在较低层,它们本身必须最终位于操作系统和硬件上 . 我们将始终需要一种最适合构建该层的语言:C / C / Assembler .

附录:C 11/14继续扩展C支持更高级别抽象和系统的能力 . 线程,同步,精确的内存模型,更精确的抽象机器定义使C开发人员能够实现许多高级抽象,其中一些高级语言曾经拥有独占域,同时继续提供接近 - 金属性能和出色的可预测性(即最小的运行时子系统) . 对于那些想要它的人来说,也许在C的未来版本中有选择地启用反射工具 - 或者也许库会提供这样的运行时服务(现在可能有一个,或者在boost中有一个开头?) .

3 years ago

在C中使用反射的情况很多,使用模板元编程等编译时结构无法充分解决 .

N3340提出了丰富的指针,作为在C中引入反射的一种方式 . 除了你使用它之外,它解决了不支付功能的问题 .

3 years ago

Reflection can be and has been implemented in c++ before.

它不是本机c功能,因为它具有很高的成本(内存和速度),默认情况下不应该由语言设置 - 语言是“默认的最大性能” .

因为你不应该为你不需要的东西买单,而且正如你自己说的那样它在编辑器中比在其他应用程序中需要更多,然后它应该只在您需要的地方实现,而不是“强制”到所有代码(您不需要反映您将在编辑器中使用的所有数据或其他类似的应用) .

3 years ago

如果您真的想了解围绕C的设计决策,请查找Ellis和Stroustrup的The Annotated C++ Reference Manual副本 . 它不是最新的标准,但它符合原始标准,并解释了事情是如何工作的,经常是如何实现的 .

3 years ago

C没有反射的原因是这需要编译器将符号信息添加到目标文件中,例如类类型具有的成员,有关成员的信息,有关函数和所有内容的信息 . 这实际上会使包含文件无用,因为声明传送的信息将从那些目标文件(模块)中读取 . 在C中,类型定义可以在程序中多次出现,包括相应的头文件(前提是所有这些定义都相同),因此必须决定在哪里放置有关该类型的信息,就像命名一个复杂在这里 . 由C编译器完成的积极优化可以优化数十个类模板实例化,这是另一个优点 . 这是可能的,但由于C与C兼容,这将成为一个尴尬的组合 .

3 years ago

根据Alistair Cockburn的说法,subtyping can't be guaranteed in a reflective environment .

反射与潜在的打字系统更相关 . 在C中,你知道你有什么类型,你知道你可以用它做什么 .

3 years ago

对具有它的语言的反思是编译器愿意在目标代码中留下多少源代码以启用反射,以及有多少分析机制可用于解释反映的信息 . 除非编译器保留所有源代码,否则反射将限制其分析源代码的可用事实的能力 .

C编译器不会在语言中得到反射 . (Java和C#编译器只保留类,方法名称和返回类型,因此您可以获得一些反射数据,但您可以't inspect expressions or program structure, and that means even in those 442085 languages the information you can get is pretty sparse and consequently you really can'进行大量分析) .

但是你可以走出语言并获得全面的反射功能 . 关于reflection in C的另一个堆栈溢出讨论的答案讨论了这一点 .