2016-12-14 167 views
2

假设我有一个用C编写的程序,我有两台相同的计算机,一台运行Windows,另一台运行linux。由于计算机是相同的,它们的处理器具有相同的指令集,因此编译后的机器代码应该相同。那么为什么我需要编译我的程序两次?假设我不调用任何与操作系统相关的函数,或者依赖于实际操作系统的东西。为什么机器代码依赖于操作系统类型?

+6

可执行格式不同。 –

+7

您无法避免调用OS函数或调用OS函数的东西。如果您希望程序具有可见效果,例如打印号码,则不适用。 –

+0

他们为什么不同?它与保护模式/内核模式有关吗? 如果处理器是相同的,他们不应该运行相同的机器码吗? –

回答

6

机器码不依赖于操作系统,它对于同一个CPU是一样的。

如果你在目标CPU模式下(比如说x86 32b)做了一个与操作系统无关的机器代码,并将其加载到某些ROM内存中,这样它就可用了,你可以在Windows中映射这部分ROM,在linux中(通过完全不同的OS API来映射物理内存并赋予其可执行权限),并跳到那里..并且ROM中的机器代码将以相同的方式运行。

那么,为什么我需要编译我的程序两次?假设我不调用任何与操作系统相关的函数,或者依赖于实际操作系统的东西。

您不需要。但通常你需要一些入口点进入你的代码,通常如何提供通用入口点最简单的方法是遵循操作系统定义的ABI(应用程序二进制接口),例如在32b窗口中你从堆栈中读取参数,在64b中你在寄存器中接收参数(如果可能的话)。如果您不会调整您的过程序言代码以正确的方式选择参数,那么它将在“其他”操作系统中输入的错误超出其写入的操作。

但是机器代码本身与CPU指令相同。这就是说,在x86上,由于历史向后兼容性,情况会更加复杂,所以CPU可以处于16b模式,32b [受保护]模式(它们中的几个加上不同的设置)或64b模式。 80386 CPU指令mov eax,1对于16b模式和32b模式具有不同的机器码编码。

但是,只要您的目标是相同的CPU模式,相同指令的机器代码就会以相同的方式编译。你只需要写出不同的来源来遵循不同的ABI。

而可执行文件......每种格式都不同,它甚至不是“每个操作系统”,再次由于历史原因,几乎所有的x86操作系统都支持多种可执行文件格式,因此存储在文件中的机器代码周围的元数据(在将机器代码加载到内存中并将其设置为运行时由OS使用)完全不同。

实用的例子是linux应用wine,它可以执行windows可执行文件,提供虚拟操作系统钩子点来模拟windows操作系统,并且通过理解windows可执行文件,正确地将它们加载到内存中。这种Windows应用程序的机器码本身运行,没有任何补丁。

+1

另外一个注意事项。汇编程序通常会生成“目标文件”,它们又是工具链特定的格式,因此Microsoft Visual Studio使用不同的“.obj”文件来存储相同的组装机器代码,而不是使用Linux上的gcc生成“.o”。目标文件的机器代码部分是相同的,但允许链接此文件的元数据可能完全不同(加上不同格式的调试元数据等)。所以这是另一个原因,为什么你必须多次编译同一个源文件,但它不是per-OS,而是每个工具链。 – Ped7g

+2

另外,ABI在字体大小上的差异:'long'在x86-64 Windows上是32位,而在System V x86-64 ABI中是64位。因此,对于不同的ABI编译时,相同的C结构可能意味着不同的东西,更不用说单个变量是不同的大小(因此在机器代码中需要不同的操作数大小,以及不同的本地堆栈布局等) –

+2

比“每工具链”。它是每个工具链版本。中间文件的二进制兼容性并不能保证来自特定供应商的所有工具永久存在。我不确定GCC人员是否试图在这里提供向后兼容性,但是Microsoft明确没有。 (实际上,这颇具讽刺意味。)交叉工具链的唯一作用是调试信息,因为它具有标准化的格式。但是,当然,关于标准的最好的事情是有很多可供选择的:COFF,ELF,CodeView,PDB,... :-) –

相关问题