为什么生成的二进制文件这么大

Why are generated binaries so large?

本文关键字:二进制文件 为什么      更新时间:2023-10-16

为什么我在编译C++程序时生成的二进制文件如此之大(就像很容易达到源代码文件大小的 10 倍)?与不需要这种编译的解释型语言相比,这有什么优势(因此程序大小只是代码文件的大小)?

现代解释型语言通常会将代码编译为某种表示形式,以便更快地执行......它可能不会写到磁盘,但肯定不能保证程序以更紧凑的形式表示。 一些解释器不遗余力地生成机器代码(例如Java JIT)。 然后是解释器本身坐在内存中,这可能很大。

几点:

  • 源代码中的命令越复杂,执行它们可能需要的机器代码操作就越多。 因此,更高级的语言功能往往具有更高的编译代码与源代码的比率。 这不一定是一件坏事:把它想象成"我只需要说一点我想做的事情,它就会推断出所有这些必要的步骤"。 编程的挑战是确保它们是必要的 - 这需要良好的库和程序设计。
  • 编译器经常故意决定牺牲一些可执行文件大小来换取更快的预期执行速度:内联代码与外联代码是这种折衷的一部分,尽管对于小函数来说,两者都可能始终更紧凑。
  • 更复杂的运行时环境(例如,添加对C++异常的支持)可能涉及一些额外的代码,这些代码在程序首次开始为该语言功能构建必要的环境时运行。
  • 库功能可能没有可比性。 除了你很可能不得不自己追踪并非常清楚使用的附加库(例如.XML,PDF解析,OpenGL)之外,语言经常悄悄地使用支持库来看起来像语言特性和功能。 其中任何一个都可以大得惊人。
    • 例如,许多解释器只是公开 C 库的 printf() 语句或类似的东西,而对于输出格式C++具有ostream - 一个更复杂、可扩展和类型安全的系统,具有跨函数调用的持久状态(无论好坏),查询和设置该状态的例程,额外的可自定义缓冲层,可自定义的字符类型和本地化, 通常,许多小的内联函数可能会导致更小更大的程序,具体取决于确切的用途和编译器设置。 最佳方法取决于您的应用程序和内存与性能目标。
  • 内置语言语句的编译方式可能不同:一个switch整数表达式,有 100 个大小写标签随机分布在 1 到 1000 之间:一个编译器/语言可能决定"打包"这 100 个大小写并执行二叉搜索以查找匹配项,另一个编译器/语言使用由 1000 个元素组成的稀疏填充数组并执行直接索引(这会浪费可执行文件中的空间,但通常会使代码更快)。 因此,很难根据可执行文件大小得出结论。

通常,随着程序变得越来越大和越来越复杂,内存使用和执行速度变得越来越重要。 您看不到操作系统、企业 Web 服务器或功能齐全的商业文字处理器等用解释型语言编写的系统,因为它们没有可扩展性。

解释型语言假定解释器可用,而编译的程序在大多数情况下是独立的。

举一个微不足道的例子:假设你有一个单行程序

print("hello world")

那个"打印"有什么作用?当然,很明显您要求其他代码来做一些工作?而且这些代码不是免费的,需要运行的总和远远超过你编写的代码行数。在更现实的程序中,您可以利用许多复杂的库来管理窗口和其他UI功能,网络,数据库等。现在,无论该代码是捆绑到您的应用程序中还是从 DLL 加载,还是存在于解释器中,它都必须在某个地方。

在编译和解释之间有很多权衡,以及中间解决方案,如Java的编译/字节码互译方法。例如,您可以考虑

    每次运行时解释
  • 源代码与运行已编译代码的运行时成本
  • 解释器的可移植性优势 - 您需要为不同的平台编译应用程序的单独版本。

通常,程序是用更高级的语言编写的,要使这些程序由CPU执行,必须将程序转换为机器码。此转换由编译器解释器完成。

编译器 只进行一次转换,而解释器通常在每次执行程序时转换它。

解释程序的运行速度比编译程序慢得多,因为解释器每次执行程序时都必须分析程序中的每个语句,然后执行所需的操作,而编译的代码只是在编译确定的固定上下文中执行操作(这是存在大型二进制文件的原因)。

解释器的另一个缺点是它们必须作为运行源代码的附加软件存在于环境中