为什么我编译 C++ 程序时生成的二进制文件如此之大(就像源代码文件大小的 10 倍一样)?与不需要这种编译的解释语言相比(因此程序大小只是代码文件的大小),这提供了哪些优势?
4 回答
现代解释语言通常会将代码编译为某种表示形式以便更快地执行……它可能不会被写入磁盘,但肯定不能保证程序以更紧凑的形式表示。一些解释器不遗余力地生成机器代码(例如Java JIT)。然后是解释器本身坐在内存中,可能很大。
几点:
- 源代码中的命令越复杂,执行它们可能需要的机器代码操作就越多。因此,高级语言特性往往具有更高的编译代码与源代码比率。这不一定是坏事:把它想象成“我只需要说一点我想要做的事情,它就会推断出所有这些必要的步骤”。编程中的挑战是确保它们是必要的——这需要良好的库和程序设计。
- 编译器经常故意决定用一些可执行文件大小来换取更快的预期执行速度:内联代码与外联代码是这种折衷的一部分,尽管对于小型函数来说,两者都可能始终更紧凑。
- 更复杂的运行时环境(例如添加对 C++ 异常的支持)可能涉及一些额外的代码,这些代码在程序第一次开始为该语言特性构建必要的环境时运行。
- 库功能可能无法比较。除了您很可能不得不自己追踪并非常了解使用的那种附加库(例如 XML、PDF 解析、OpenGL)之外,语言通常会悄悄地使用支持库来实现看起来像语言功能的功能和功能。这些中的任何一个都可能大得惊人。
- 例如,许多解释器只公开 C 库的
printf()
语句或类似的东西,而对于输出格式,C++ 具有ostream
- 一个更复杂、可扩展和类型安全的系统,具有(无论好坏)跨函数调用的持久状态、查询和设置的例程该状态,可自定义缓冲的附加层,可自定义的字符类型和本地化,以及通常许多小的内联函数,这些函数可以根据确切的使用和编译器设置导致更小或更大的程序。什么是最好的取决于您的应用程序和内存与性能目标。
- 例如,许多解释器只公开 C 库的
- 内置语言语句的编译方式可能不同:a
switch
在整数表达式上,并且有 100 个案例标签在 1 到 1000 之间随机分布:一种编译器/语言可能决定“打包”这 100 个案例并进行二进制搜索以查找匹配项,另一种使用一个由 1000 个元素组成的稀疏数组并进行直接索引(这会浪费可执行文件中的空间,但通常会使代码更快)。因此,很难根据可执行文件大小得出结论。
通常,随着程序变得越来越大和越来越复杂,内存使用和执行速度变得越来越重要。你看不到操作系统、企业网络服务器或用解释语言编写的全功能商业文字处理器等系统,因为它们不具备可扩展性。
解释语言假定解释器可用,而编译程序在大多数情况下是独立的。
举一个小例子:假设你有一个单行程序
print("hello world")
那个“打印”是做什么的?当然很明显,您要求其他代码做一些工作?而且该代码不是免费的,需要运行的总和远远超过您编写的代码行数。在更现实的程序中,您会利用许多复杂的库来管理窗口和其他 UI 功能、网络、数据库等。现在,无论该代码是捆绑到您的应用程序中还是从 DLL 中加载还是存在于解释器中,它都必须在某个地方。
在编译和解释以及中间解决方案(如 Java 的编译/字节码解释方法)之间有很多权衡。例如,您可能会考虑
- 每次运行时解释源代码与运行已编译代码的运行时成本
- 解释器的可移植性优势——你需要为不同的平台编译不同版本的应用程序。
通常,程序是用高级语言编写的,为了让这些程序由 CPU 执行,必须将程序转换为机器代码。此转换由Compiler或Interpreter完成。
编译器只进行一次转换,而解释器通常在每次执行程序时进行转换。
解释的程序比编译的程序运行得慢得多,因为解释器必须在每次执行程序时分析程序中的每个语句,然后执行所需的操作,而编译的代码只是在由编译确定的固定上下文中执行操作(这是存在大型二进制文件的原因)。
解释器的另一个缺点是它们必须作为附加软件存在于环境中才能运行源代码。