编译一个程序两次是否会产生一个位对位相同的二进制文件?

Dav*_*vid 26 compile checksum

如果我将一个程序编译成单个二进制文件,进行校验和,然后在同一台机器上使用相同的编译器和编译器设置重新编译它,并对重新编译的程序进行校验和校验,校验和会失败吗?

如果是这样,这是为什么?如果不是,使用不同的 CPU 会导致不同的二进制文件吗?

ric*_*ici 19

  1. 在同一台机器上用相同的设置编译相同的程序:

    尽管明确的答案是“视情况而定”,但可以合理地预期大多数编译器在大多数时间都是确定性的,并且生成的二进制文件应该是相同的。事实上,一些版本控制系统依赖于此。不过,总有例外。这很可能是一些编译器的地方将决定插入一个时间戳或一些这样的(IIRC,德尔福做,例如)。或者构建过程本身可能会这样做;我已经看到 C 程序的 makefile 将预处理器宏设置为当前时间戳。(不过,我想这会算作不同的编译器设置。)

    另外,请注意,如果您静态链接二进制文件,那么您就有效地合并了机器上所有相关库的状态,其中任何一个库的任何更改也会影响您的二进制文件。因此,相关的不仅仅是编译器设置。

  2. 在具有不同 CPU 的不同机器上编译相同的程序。

    在这里,所有赌注都取消了。大多数现代编译器都能够进行特定于目标的优化;如果启用此选项,则二进制文件可能会有所不同,除非 CPU 相似(即使如此,也有可能)。另外,请参阅上面关于静态链接的说明:配置环境远远超出了编译器设置。除非您有非常严格的配置控制,否则两台机器之间极有可能存在差异。


Cir*_*郝海东 9

  • -frandom-seed=123控制一些 GCC 内部随机性。man gcc说:

    这个选项提供了一个种子,GCC 使用它代替随机数来生成某些符号名称,这些名称在每个编译文件中都必须不同。它还用于在覆盖数据文件和生成它们的目标文件中放置唯一的标记。您可以使用 -frandom-seed 选项生成可重现的相同目标文件。

  • __FILE__: 把源放在一个固定的文件夹中(例如/tmp/build

  • 对于__DATE__, __TIME__, __TIMESTAMP__
    • libfaketime : https://github.com/wolfcw/libfaketime
    • 覆盖这些宏 -D
    • -Wdate-timeor -Werror=date-time:如果使用__TIME__,__DATE____TIMESTAMP__are ,则警告或失败。Linux 内核 4.4 默认使用它。
  • 使用D标志ar,或使用https://github.com/nh2/ar-timestamp-wiper/tree/master擦除邮票
  • -fno-guess-branch-probability较旧的手动版本说它是不确定性的来源,但现在不是了。不确定这是否涵盖-frandom-seed

Debian Reproducible builds 项目试图逐字节标准化 Debian 软件包,最近获得了Linux 基金会的资助。这不仅包括编译,还应该引起人们的兴趣。

Buildroot有一个BR2_REPRODUCIBLE选项,可以在包级别提供一些想法,但在这一点上还远未完成。

相关主题:


hea*_*ase 8

您要问的是“输出确定性”。如果您编译了一次程序,立即再次编译它,您可能最终会得到相同的输出文件。然而,如果有任何变化——即使是很小的变化——尤其是在编译程序使用的组件中,那么编译器的输出也可能会发生变化。

  • 确实很好。[这篇文章](http://blog.mindfab.net/2013/12/on-way-to-deterministic-binariy-gcc.html) 有一些非常有趣的观察。特别是,在某些情况下,使用 GCC 编译可能_不是_确定性的输入,例如它如何在匿名命名空间中破坏函数,为此它在内部使用随机数生成器。要在这种特殊情况下获得确定性,请通过指定选项“-frandom-seed=string”来提供初始随机种子。 (2认同)

ta.*_*.is 7

重新编译程序是否会产生一个位对位相同的二进制文件?

对于所有编译器?不可以。至少 C# 编译器是不允许的。

Eric Lippert对为什么编译器的输出不是确定性的进行了非常彻底的分解

[T]C# 编译器的设计永远不会产生两次相同的二进制文件。每次运行时,C# 编译器都会在每个程序集中嵌入一个新生成的 GUID,从而确保没有两个程序集完全相同。引用 CLI 规范:

Mvid 列应索引一个唯一的 GUID [...],用于标识模块的此实例。[...] 应该为每个模块新生成 Mvid [...] 虽然 [runtime] 本身不使用 Mvid,但其他工具(例如调试器 [...])依赖于以下事实: Mvid 几乎总是从一个模块到另一个模块不同。

尽管它特定于某个版本的 C# 编译器,但本文中的许多要点可以应用于任何编译器。

首先,我们假设每次总是以相同的顺序获得相同的文件列表。但这在某些情况下取决于操作系统。当您说“csc *.cs”时,操作系统提供匹配文件列表的顺序是操作系统的实现细节;编译器不会将该列表排序为规范顺序。

  • ECMA 标准不必有时间戳或 MVID 差异。如果没有这些,至少可以在 C# 中使用相同的二进制文件。因此,主要原因是一个有问题的设计决策,而不是真正的技术限制。 (5认同)