代码之家  ›  专栏  ›  技术社区  ›  Maxbit

为什么我们需要为不同的平台(如Windows/Linux)编译?

  •  9
  • Maxbit  · 技术社区  · 7 年前

    我已经学习了关于CPU/ASM/C的基本知识,不明白为什么我们需要为不同的操作系统目标编译不同的C代码。编译器所做的是创建汇编程序代码,然后将其组装成二进制机器代码。当然,由于指令集体系结构不同,每个CPU体系结构(如ARM)的ASM代码也不同。

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

    4 回复  |  直到 7 年前
        1
  •  23
  •   StaceyGirl    3 年前

    尽管CPU是一样的,但仍有许多不同之处:

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

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

    • 与操作系统无关,但由不同编译器编译的程序可能不可互操作:可能会使用不同的标准库,C++名称混乱等情况可能会有所不同,使库之间无法相互链接,C++异常实现可能不可互操作。
    • 不同的文件系统结构。Windows上的“”和Unix上的“/”之间不仅有区别,而且还有一些“特殊文件”可能存在,也可能不存在,例如“/dev/null”。

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

    当模拟POSIX系统调用时,像Cygwin这样的源兼容性层也可能效率低下,例如 fork() 在Windows模型的顶部。但总的来说,Cygwin的工作比葡萄酒容易:项目需要 重新编译 在Cygwin的领导下。它不尝试在Windows下运行本机Linux二进制文件。

        2
  •  2
  •   SoronelHaetir    7 年前

    除此之外,即使使用相同的指令,调用约定也可能有所不同,即参数在堆栈或寄存器中的位置、找到的顺序参数、在函数调用中必须保留哪些寄存器、返回值如何从被调用方传递给调用方。

        3
  •  1
  •   old_timer    7 年前

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

    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   
    

    细微的差别是肯定的,但关键是这是两个完全不同的操作系统,程序的入口/出口(上面没有显示的代码有很多不同,而不仅仅是这个程序中的小规范if主代码)。

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

    这甚至可以指出,gcc编译的windows程序并不能完全兼容所有版本的windows,您不能只说“windows”。linux也是如此。它们独立于目标进行内部更改,因此操作系统之间存在不兼容的差异。仅仅因为砖和灰泥是一样的,并不意味着两栋建筑是一样的。

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

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

        4
  •  -1
  •   Morse unmounted    7 年前

    您可能需要检查其他答案。

    它的种类 duplicate 问题 除了它代表C而不是C++

    您可以在此处检查编译步骤的过程:

    C compilation steps

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