为什么需要前向声明?
可能的重复:
C++ 应该消除头文件吗?
在 C# 和 Java 等语言中,不需要声明(例如)使用之前的一个类。如果我理解正确的话,这是因为编译器对代码进行了两次传递。在第一个中,它只是“收集可用的信息”,在第二个中,它检查代码是否正确。
在 C 和 C++ 中,编译器只执行一次传递,因此当时所有内容都需要可用。
所以我的问题基本上是为什么在 C 和 C++ 中不这样做。难道它不会消除对头文件的需要吗?
Possible Duplicate:
Should C++ eliminate header files?
In languages like C# and Java there is no need to declare (for example) a class before using it. If I understand it correctly this is because the compiler does two passes on the code. In the first it just "collects the information available" and in the second one it checks that the code is correct.
In C and C++ the compiler does only one pass so everything needs to be available at that time.
So my question basically is why isn't it done this way in C and C++. Wouldn't it eliminate the needs for header files?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
简而言之,从 C 语言被定义到 25 年后 Java 出现,计算能力和资源呈指数级增长。
更长的答案......
编译单元的最大大小(编译器在单个块中处理的代码块)将受到编译计算机的内存量的限制有。为了处理您输入到机器代码中的符号,编译器需要将所有符号保存在查找表中,并在代码中遇到它们时引用它们。
当 C 于 1972 年创建时,计算资源更加稀缺且溢价很高——大多数系统根本无法立即存储复杂程序的整个符号表所需的内存。固定存储也很昂贵,而且速度非常慢,因此像虚拟内存或在磁盘上存储部分符号表这样的想法根本不允许在合理的时间范围内进行编译。
解决这个问题的最佳解决方案是让人提前找出哪些编译单元需要符号表的哪些部分,从而将代码分成更小的部分。对程序员施加一个相当小的任务来声明他将使用什么,可以节省让计算机在整个程序中搜索程序员可以使用的任何内容的巨大努力。
它还使编译器不必对每个源文件进行两次传递:第一次对内部的所有符号进行索引,第二次解析引用并查找它们。当您处理磁带时,寻道时间以秒为单位,读取吞吐量以每秒字节数(而不是千字节或兆字节)为单位,这是非常有意义的。
C++ 虽然创建于近 17 年后,但被定义为 C 的超集,因此必须使用相同的机制。
到 1995 年 Java 出现时,普通计算机已经拥有足够的内存,即使对于复杂的项目来说,保存符号表也不再是一个沉重的负担。而且Java 的设计初衷并不是向后兼容C,因此它没有必要采用遗留机制。 C# 同样没有受到阻碍。
结果,他们的设计者选择将划分符号声明的负担从程序员身上转移回来,并再次将其放在计算机上,因为其成本与编译总工作量相比是最小的。
The short answer is that computing power and resources advanced exponentially between the time that C was defined and the time that Java came along 25 years later.
The longer answer...
The maximum size of a compilation unit -- the block of code that a compiler processes in a single chunk -- is going to be limited by the amount of memory that the compiling computer has. In order to process the symbols that you type into machine code, the compiler needs to hold all the symbols in a lookup table and reference them as it comes across them in your code.
When C was created in 1972, computing resources were much more scarce and at a high premium -- the memory required to store a complex program's entire symbolic table at once simply wasn't available in most systems. Fixed storage was also expensive, and extremely slow, so ideas like virtual memory or storing parts of the symbolic table on disk simply wouldn't have allowed compilation in a reasonable timeframe.
The best solution to the problem was to chunk the code into smaller pieces by having a human sort out which portions of the symbol table would be needed in which compilation units ahead of time. Imposing a fairly small task on the programmer of declaring what he would use saved the tremendous effort of having the computer search the entire program for anything the programmer could use.
It also saved the compiler from having to make two passes on every source file: the first one to index all the symbols inside, and the second to parse the references and look them up. When you're dealing with magnetic tape where seek times were measured in seconds and read throughput was measured in bytes per second (not kilobytes or megabytes), that was pretty meaningful.
C++, while created almost 17 years later, was defined as a superset of C, and therefore had to use the same mechanism.
By the time Java rolled around in 1995, average computers had enough memory that holding a symbolic table, even for a complex project, was no longer a substantial burden. And Java wasn't designed to be backwards-compatible with C, so it had no need to adopt a legacy mechanism. C# was similarly unencumbered.
As a result, their designers chose to shift the burden of compartmentalizing symbolic declaration back off the programmer and put it on the computer again, since its cost in proportion to the total effort of compilation was minimal.
底线:编译器技术的进步使得前向声明变得不必要。另外,计算机的速度要快数千倍,因此可以进行额外的计算来处理缺少前向声明的情况。
C 和 C++ 比较古老,并且在需要节省每个 CPU 周期时才被标准化。
Bottom line: there have been advances in compiler technology that make forward declarations unnecessary. Plus computers are thousands of times faster, and so can make the extra calculations necessary to handle the lack of forward declarations.
C and C++ are older and were standardized at a time when it was necessary to save every CPU cycle.
不,它不会消除头文件。它将消除使用标头在同一文件中声明类/函数的要求。不过,标头的主要原因是不在同一文件中声明内容。标头的主要原因是声明在其他文件中定义的内容。
无论好坏,C(和 C++)的语义规则强制要求“单遍”风格的行为。举个例子,考虑这样的代码:
i=1
分配给全局,而不分配给f( 内部定义的)
。这是因为在赋值时,尚未看到i
的本地定义,因此未将其考虑在内。您仍然可以使用两遍编译器遵循这些规则,但这样做可能并不简单。我还没有检查他们的规范来确定,但我的直接猜测是 Java 和 C# 在这方面与 C 和 C++ 不同。编辑:由于评论说我的猜测不正确,所以我做了一些检查。根据《Java 语言参考》第 14.4.2 节,Java 似乎遵循与 C++ 非常接近的规则(略有不同,但差别不大。
至少在我读到C# 语言规范< /a>,(警告:Word 文件)但是,它不同。它(第 3.7.1 节)表示:“局部变量声明中声明的局部变量的范围(第 8.5 节)。 .1) 是声明发生的块。”
这似乎是说,在 C# 中,局部变量应该在声明它的整个块中可见,因此代码类似于我给出的例子中,赋值是给局部变量,而不是全局变量,
所以,我的猜测是对的:Java 遵循(在这方面与 C++ 几乎相同的规则,但 C# 没有。
No, it would not obviate header files. It would eliminate the requirement to use a header to declare classes/functions in the same file. The major reason for headers is not to declare things in the same file though. The primary reason for headers is to declare things that are defined in other files.
For better or worse, the rules for the semantics of C (and C++) mandate the "single pass" style behavior. Just for example, consider code like this:
The
i=1
assigns to the global, not the one defined inside off()
. This is because at the point of the assignment, the local definition ofi
hasn't been seen yet so it isn't taken into account. You could still follow these rules with a two-pass compiler, but doing so could be non-trivial. I haven't checked their specs to know with certainty, but my immediate guess would be that Java and C# differ from C and C++ in this respect.Edit: Since a comment said my guess was incorrect, I did a bit of checking. According to the Java Language Reference, §14.4.2, Java seems to follow pretty close to the same rules as C++ (a little different, but not a whole lot.
At least as I read the C# language specification, (warning: Word file) however, it is different. It (§3.7.1) says: "The scope of a local variable declared in a local-variable-declaration (§8.5.1) is the block in which the declaration occurs."
This appears to say that in C#, the local variable should be visible throughout the entire block in which it is declared, so with code similar to the example I gave, the assignment would be to the local variable, not the global.
So, my guess was half right: Java follows (pretty much0 the same rule as C++ in this respect, but C# does not.
这是因为 C/C++ 中的编译模块较小。在 C/C++ 中,每个 .c/.cpp 文件都是单独编译的,创建一个 .obj 模块。因此,编译器需要有关在其他编译模块中声明的类型和变量的信息。此信息以前向声明的形式提供,通常在头文件中。
另一方面,C# 将多个 .cs 文件同时编译成一个大的编译模块。
事实上,当从 C# 程序引用不同的编译模块时,编译器需要像 C++ 编译器一样了解声明(类型名称等)。该信息是直接从编译的模块中获取的。在 C++ 中,相同的信息是显式分离的(这就是为什么您无法从 C++ 编译的 DLL 中找到变量名称,但可以从 .NET 程序集中确定它)。
This is because of smaller compilation modules in C/C++. In C/C++, each .c/.cpp file is compiled separately, creating an .obj module. Thus the compiler needs the information about types and variables, declared in other compilation modules. This information is supplied in form of forward declarations, usually in header files.
C#, on the other side, compiles several .cs files into one big compilation module at once.
In fact, when referencing different compiled modules from a C# program, the compiler needs to know the declarations (type names etc.) the same way as C++ compiler does. This information is obtained from the compiled module directly. In C++ the same information is explicitly separated (that's why you cannot find out the variable names from C++-compiled DLL, but can determine it from .NET assembly).
C++ 中的前向声明是一种向编译器提供有关当前编译源可能使用的其他代码片段的元数据的方法,以便编译器可以生成正确的代码。
该元数据可以来自链接库/组件的作者。但是,它也可以自动生成(例如,有一些工具可以为 COM 对象生成 C++ 头文件)。无论如何,表达元数据的 C++ 方式是通过需要包含在源代码中的头文件。
C#/.Net 在编译时也会消耗类似的元数据。但是,该元数据是在构建其所应用的程序集时自动生成的,并且通常嵌入其中。因此,当您在 C# 项目中引用程序集时,您实际上是在告诉编译器“请在该程序集中查找您需要的元数据”。
换句话说,C# 中的元数据生成和使用对开发人员来说更加透明,使他们能够专注于真正重要的事情 - 编写自己的代码。
将有关代码的元数据与程序集捆绑在一起还有其他好处。反射、代码发出、动态序列化——它们都依赖于元数据才能在运行时生成正确的代码。
与此类似的 C++ 是 RTTI,尽管由于实现不兼容而没有被广泛采用。
The forward declarations in C++ are a way to provide metadata about the other pieces of code that might be used by the currently compiled source to the compiler, so it can generate the correct code.
That metadata can come from the author of the linked library/component. However, it can also be automatically generated (for example there are tools that generate C++ header files for COM objects). In any case, the C++ way of expressing that metadata is through the header files you need to include in your source code.
The C#/.Net also consume similar metadata at compile time. However, that metadata is automatically generated when the assembly it applies to is built and is usually embedded into it. Thus, when you reference in your C# project an assembly, you are essentially telling the compiler "look for the metadata you need in this assembly as well, please".
In other words, the metadata generation and consumption in C# is more transparent to the developers, allowing them to focus on what really matters - writing their own code.
There are also other benefits to having the metadata about the code bundled with the assembly as well. Reflection, code emitting, on-the-fly serialization - they all depend on the metadata to be able to generate the proper code at run-time.
The C++ analogue to this would be RTTI, although it's not widely-adopted due ot incompatible implementations.
来自 C# 内部所有内容的博主 Eric Lippert:http://blogs.msdn.com/ericlippert/archive/2010/02/04/how-many-passes.aspx:
总而言之,使用某些东西不需要在 C# 中声明它,而在 C++ 中则需要声明它。这意味着在 C++ 中,您需要显式声明事物,并且使用头文件更方便、更安全,这样您就不会违反 一个定义规则。
From Eric Lippert, blogger of all things internal to C#: http://blogs.msdn.com/ericlippert/archive/2010/02/04/how-many-passes.aspx:
To sum up, using something does not require declaring it in C#, whereas it does in C++. That means that in C++, you need to explicitly declare things, and it's more convenient and safe to do that with header files so you don't violate the One Definition Rule.