【问题标题】:Why do we need to compile for different platforms (e.g. Windows/Linux)?为什么我们需要针对不同的平台(例如 Windows/Linux)进行编译?
【发布时间】:2018-06-22 11:11:01
【问题描述】:

我了解了有关 CPU/ASM/C 的基础知识,但不明白为什么我们需要针对不同的操作系统目标以不同的方式编译 C 代码。编译器所做的是创建汇编程序代码,然后将其汇编为二进制机器代码。由于指令集架构不同,ASM 代码当然因 CPU 架构(例如 ARM)而异。

但由于 Linux 和 Windows 在同一个 CPU 上运行,因此 MOVE/ADD/... 等机器操作应该是相同的。虽然我知道有一些特定于操作系统的功能,比如打印到终端,但这个功能可以由 stdio.h 的不同实现提供,例如。而且,我仍然可以创建一个非常基本的程序,只计算 a + b 而不打印任何内容,因此我不需要任何特定于操作系统的代码。为什么我仍然需要针对 Linux 和 Windows 进行编译,而不仅仅是向我的 Linux 可执行文件添加 .exe 扩展?

【问题讨论】:

  • 由于可执行格式不同,共享库(或Windows中的DLL)不同,libc实现不同等
  • 除了你的计算之外,每个可执行文件在启动和终止时都会做很多额外的隐藏工作。这项工作是特定于操作系统的。这个隐藏的启动和终止代码必须为每个操作系统单独生成。
  • 但我不明白为什么可执行格式不同。最后,所有可执行文件都是机器操作,在操作系统之间没有区别,而是从架构到架构。共享库由操作系统处理,所以我只调用 fopen 之类的函数,而不关心哪个操作系统正在处理调用(我想这就像一个微服务架构)。你明白我的问题吗,@Pablo?
  • 可执行格式是不同的,因为微软想要那样——问问他们。他们没有理由不能使用现有的格式(尽管这些库仍然会有不同的实现)。
  • 二进制格式不同,因为它们的设计不同。 Linux 使用 ELF,这是不同的,Windows 使用(据我所知)Portable Executable,这是不同的。将使用相同的汇编指令,但方式不同。

标签: c assembly compilation cpu cpu-architecture


【解决方案1】:

您可能想检查其他答案。

类似duplicate 的问题除了它是针对 C 而不是 C++

您可以在这里查看编译步骤的过程:

C compilation steps

简而言之,尽管 C 可以跨平台运行,但由于编译器的原因,它不能跨平台编译。

【讨论】:

    【解决方案2】:

    除了其他一切,即使使用相同的指令,调用约定也可能不同,即参数在堆栈或寄存器中的放置,找到参数的顺序,在函数调用中必须保留哪些寄存器,如何返回值从被调用者传递给调用者。

    【讨论】:

      【解决方案3】:

      虽然CPU相同,但还是有很多不同:

      • 不同的可执行格式。
      • 可能会使用不同的调用约定。例如 Windows x64 passes integer args in different registers than the x86-64 System V ABI 和其他几个显着差异,包括 Windows 中保留调用的 xmm6..15,与其他 x86-64 不同。
      • 关于堆栈结构的不同约定。类 Unix 系统有一个“red zone”的概念来帮助编译器生成更短的代码。执行环境必须遵守这样的概念以避免堆栈损坏。
      • 程序链接到具有不同 ABI 的不同标准库 - 字段顺序可能不同,可能存在其他扩展字段。
      • 在 C 和 C++ 中,某些数据类型的大小取决于操作系统。例如在 x86_64 long 在 Linux 上是 8 字节,但在 Windows 上是 4 字节。 (类型大小和所需的对齐方式是构成 ABI 的另一部分,还有结构/类布局规则。)
      • 标准库可以提供不同的功能集。在 Linux 上,libc 直接提供像 snprintf 这样的函数,但在 Windows 上,snprintf 可能在一个实际从 C 运行时调用另一个函数的头文件中实现为 static inline 函数。这对程序员来说是透明的,但会为可执行文件生成不同的导入列表。
      • 程序以不同的方式与操作系统交互:在 Linux 上,程序可能会直接进行系统调用,因为它们已记录在案并且是提供的接口的一部分,而在 Windows 上,它们没有记录在案,程序应改为使用提供的函数。

        即使 Linux 程序只调用 C 库的包装函数,Windows C 库也不会有像 read()ioctl()mmap 这样的 POSIX 函数。相反,Windows 程序可能会调用VirtualAlloc,这在 Linux 上不可用。 (但使用特定于操作系统的系统调用的程序,而不仅仅是 ISO C/C++ 函数,即使在源代码级别也不能移植;它们需要#ifdef 才能仅在 Windows 上使用 Windows 系统调用。)

      • 与操作系统无关,但由不同编译器编译的程序可能无法互操作:可能使用不同的标准库,C++ 名称修改等内容可能不同,因此无法将库相互链接,C++ 异常实现可能不是- 可互操作。
      • 不同的文件系统结构。不仅 Windows 上的“\”和 Unix-like 上的“/”之间存在差异,而且还有一些“特殊文件”可能存在也可能不存在,例如“/dev/null”。

      理论上,这里列出的所有问题都可以解决:可以编写自定义加载器以支持不同的可执行格式,不同的约定和接口如果整个程序使用相同的一组,则不会造成问题。这就是为什么像 Wine 这样的项目可以在 Linux 上运行 Windows 二进制文件的原因。问题是 Wine 必须在其他操作系统提供的功能之上模拟 Windows NT 内核的功能,从而降低了实现效率。由于使用了不同的不可互操作的接口,此类程序在与本机程序交互时也存在问题。

      在 Windows 模型之上模拟像 fork() 这样的 POSIX 系统调用时,像 Cygwin 这样的源代码兼容层也可能效率低下。但总的来说 Cygwin 比 WINE 更容易:程序需要在 Cygwin 下重新编译。它不会尝试在 Windows 下运行原生 Linux 二进制文件。

      【讨论】:

      • 非常感谢您提供的详细列表,尤其是“理论上这里列出的所有问题都可以解决”的评论。我想我现在明白了。
      • 你是从哪里学到这些的?我对编程有点陌生……我确实读过三本关于 c/cpp 的书,没有提到这些东西。我还读了一本关于编译器的书;它更关心理论,并没有提及任何操作系统的具体细节。我很想阅读更多关于此的内容,但不知道在哪里看...谢谢
      • @NewbieAndCurious 只是多年的编程经验。当您尝试编写还需要在多个操作系统上工作的复杂内容时,差异变得明显。因此,任何解释特定操作系统如何工作的书都应该没问题。对于 Windows,我阅读了 Jeffrey Richter 和 Christophe Nasarre 的“Windows via C/C++”,这对我帮助很大。对于 Linux,有一些带有系统调用描述的手册页,可以帮助理解内核的工作原理。
      • @StaceyGirl 谢谢,但是有什么书可以推荐吗?可能会深入其中的一些细节(操作系统特定的调用,或 c 库实现,甚至解释 ABI 的概念)?
      【解决方案4】:

      这就像说如果我使用相同的字母表所有的书都是相同的生物课本和数学课本是相同的,因为它们使用相同的字母表有封面有一些页面等等。或者我必须去滑雪胜地并且因为他们都使用相同的字母,并且因为他们都是关于雪的,所以他们的海报和小册子是相同的。

      int main ( void )
      {
          return(27);
      }
      
      0000000000402cd0 <main>:
        402cd0:   48 83 ec 28             sub    $0x28,%rsp
        402cd4:   e8 d7 e9 ff ff          callq  4016b0 <__main>
        402cd9:   b8 1b 00 00 00          mov    $0x1b,%eax
        402cde:   48 83 c4 28             add    $0x28,%rsp
        402ce2:   c3                      retq   
      
        00000000004003e0 <main>:
        4003e0:   b8 1b 00 00 00          mov    $0x1b,%eax
        4003e5:   c3                      retq   
      

      确实存在细微差别,但关键是这是两个完全不同的操作系统,程序的进入/退出(上面没有显示大量代码会有所不同,而不仅仅是主要代码在这个程序。

      这些是不同的操作系统,它们有不同的调用不同的规则,它们是不同的,通用的指令集有些无关紧要。就像说因为我在 linux 上运行并使用 C 作为我的编程语言,那么为 arm 制作的二进制文件和为 x86 制作的二进制文件应该是相同且兼容的(因为我所说的三件事中有两件事是相同的,编程语言和操作系统但不是指令集。或者在你的情况下是编程语言和指令集但不是操作系统。)

      这甚至指出了一个 gcc 编译的 windows 程序并不完全兼容所有版本的 windows,你不能只说“windows”。 linux也是如此。它们独立于目标而在自身内部发生变化,因此操作系统之间存在不兼容的差异。仅仅因为砖和灰泥相同,并不能制造出两座相同的建筑物。

      这是 JAVA 和 Python 等语言的目的,画一条线,这条线以上的一切都是通用的和跨平台的,这条线以下的可以是平台和目标特定的,没有理由期望任何形式的跨平台兼容性。如果我们在具有 C 编译器的计算机或所有运行独立于平台的 linux 的计算机或所有运行具有编译器和相同指令集的操作系统的计算机之间具有这种兼容性,那么这些语言就不会存在。

      当您下载一些程序(如 chrome 或 7-zip 或 firefox、handbrake 等)时,有一个原因是有不同的安装程序和/或二进制文件,具体取决于操作系统和操作系统版本。指令集通常甚至没有列出,因为它被认为是 x86,但是有不同的二进制文件,如果它是这样微不足道的,那么为什么那些已经交付了这么长时间的成品的人会交付几个不同的产品版本?

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-10-18
        • 2019-05-28
        • 2022-12-05
        • 1970-01-01
        • 2012-09-09
        • 1970-01-01
        • 2021-06-27
        相关资源
        最近更新 更多