为什么需要前瞻性声明?

时间:2010-04-13 19:32:34

标签: c++ c compiler-construction declaration

  

可能重复:
  Should C++ eliminate header files?

在C#和Java等语言中,没有必要在使用它之前声明(例如)一个类。如果我理解正确,这是因为编译器对代码进行了两次传递。在第一个中它只是“收集可用信息”,在第二个中它检查代码是否正确。

在C和C ++中,编译器只进行一次传递,因此当时所有东西都需要可用。

所以我的问题基本上就是为什么不用C和C ++这样做。它不会消除对头文件的需求吗?

6 个答案:

答案 0 :(得分:41)

简短的回答是计算能力和资源在定义C的时间与Java在25年后出现的时间之间呈指数级增长。

答案越久......

编译单元的最大大小 - 编译器在单个块中处理的代码块 - 将受编译计算机具有的内存量的限制。为了处理您在机器代码中键入的符号,编译器需要将所有符号保存在查找表中,并在代码中引用它们时引用它们。

当C在1972年创建时,计算资源更加稀缺并且处于高价位 - 大多数系统都无法立即存储复杂程序的整个符号表所需的内存。固定存储也很昂贵,并且非常慢,因此虚拟内存或在磁盘上存储符号表部分等想法根本不允许在合理的时间范围内进行编译。

解决这个问题的最佳方法是将代码分成更小的部分,方法是在需要时将符号表的哪些部分放在哪个编译单元中。对程序员声明他使用的内容施加一个相当小的任务节省了计算机搜索整个程序以获得程序员可以使用的任何东西的巨大努力。

它还使编译器不必在每个源文件上进行两次传递:第一个用于索引内部的所有符号,第二个用于解析引用并查找它们。当您处理磁带时,在几秒钟内测量搜索时间,读取吞吐量以每秒字节数(不是千字节或兆字节)来衡量,这非常有意义。

C ++,虽然在大约17年后创建,但被定义为C的超集,因此必须使用相同的机制。

当Java在1995年推出时,普通计算机有足够的内存,即使对于复杂的项目,持有符号表也不再是一个沉重的负担。 Java并不是为了与C语言向后兼容而设计的,因此它不需要采用传统机制。 C#同样没有受到阻碍。

因此,他们的设计师选择将符号声明的划分负担从程序员身上移开,并再次将其放在计算机上,因为其成本与编制总工作量的比例很小。

答案 1 :(得分:5)

结论:编译器技术已取得进展,无需进行前向声明。加上计算机的速度要快几千倍,因此可以进行必要的额外计算以处理缺少前向声明。

C和C ++较旧,在需要保存每个CPU周期时都是标准化的。

答案 2 :(得分:3)

不,它不会消除头文件。它将消除使用标头在同一文件中声明类/函数的要求。标题的主要原因是来声明同一文件中的内容。标头的主要原因是声明其他文件中定义的内容。

无论好坏,C(和C ++)的语义规则都要求“单一传递”式行为。例如,考虑这样的代码:

int i;

int f() { 
     i = 1;
     int i = 2;
}

i=1分配给全局而不是 f()内定义的那个。这是因为在分配时,尚未看到i的本地定义,因此不予考虑。您仍然可以使用双遍编译器来遵循这些规则,但这样做可能非常重要。我没有确切地检查他们的规格,但我的直接猜测是Java和C#在这方面与C和C ++不同。

编辑:由于评论说我的猜测不正确,我做了一些检查。根据Java语言参考,§14.4.2,Java似乎遵循 pretty 接近与C ++相同的规则(稍微不同,但不是很多。

至少在我阅读C# language specification时,(警告:Word文件), 不同。它(§3.7.1)说:“在局部变量声明(第8.5.1节)中声明的局部变量的范围是声明发生的块。”

这似乎可以说在C#中,局部变量应该在声明它的整个块中可见,所以使用类似于我给出的示例的代码,赋值将是局部变量,而不是全局变量。

所以,我的猜测是正确的一半:Java遵循(在这方面几乎与C ++相同,但C#没有。

答案 3 :(得分:0)

这是因为C / C ++中的编译模块较小。在C / C ++中,每个.c / .cpp文件都是单独编译的,从而创建了一个.obj模块。因此,编译器需要有关在其他编译模块中声明的类型和变量的信息。此信息以正向声明的形式提供,通常在头文件中提供。

另一方面,C#会立即将几个.cs文件编译成一个大的编译模块。

实际上,当从C#程序引用不同的已编译模块时,编译器需要以与C ++编译器相同的方式知道声明(类型名称等)。此信息直接从编译模块中获取。在C ++中,相同的信息被明确分开(这就是为什么你不能从C ++中找到变量名 - 编译的DLL,但可以从.NET程序集中确定它。)

答案 4 :(得分:0)

C ++中的前向声明是一种提供有关当前编译源可能用于编译器的其他代码片段的元数据的方法,因此它可以生成正确的代码。

元数据可以来自链接库/组件的作者。但是,它也可以自动生成(例如,有些工具可以为COM对象生成C ++头文件)。在任何情况下,C ++表达元数据的方式都是通过您需要包含在源代码中的头文件。

C#/ .Net在编译时也使用类似的元数据。但是,当应用它所应用的程序集时,会自动生成该元数据,并且通常会将其嵌入到该元数据中。因此,当您在C#项目中引用程序集时,您实际上是在告诉编译器“请在此程序集中查找您需要的元数据”。

换句话说,C#中的元数据生成和消费对开发人员来说更加透明,使他们能够专注于真正重要的事情 - 编写自己的代码。

使用与程序集捆绑的代码的元数据也有其他好处。反射,代码发出,动态序列化 - 它们都依赖于元数据,以便能够在运行时生成正确的代码。

与此类似的C ++将是RTTI,尽管由于不兼容的实现而没有被广泛采用。

答案 5 :(得分:0)

来自Eric Lippert,C#内部所有内容的博主:http://blogs.msdn.com/ericlippert/archive/2010/02/04/how-many-passes.aspx

  

C#语言不需要   声明发生在使用之前,   这又有两个影响   用户和编译器编写器。 [...]

     

对编译器编写器的影响是   我们要有“两通”   编译器。在第一轮,我们看   用于声明和忽略主体。   一旦我们收集了所有的信息   来自声明的信息   我们可以从头文件中获取   C ++,我们第二次通过了   代码并生成IL   体。

总而言之,使用某些东西不需要在C#中声明它,而在C ++中则是这样。这意味着在C ++中,您需要明确声明事物,使用头文件更方便,更安全,因此您不会违反One Definition Rule