为什么我们需要为不同的平台编译(例如Windows/Linux)?
Why do we need to compile for different platforms (e.g. Windows/Linux)?
我已经了解了 CPUs/ASM/C 的基础知识,但不明白为什么我们需要为不同的 OS 目标编译不同的 C 代码。编译器所做的是创建汇编程序代码,然后将其汇编为二进制机器代码。 ASM 代码当然每个 CPU 体系结构(例如 ARM)不同,因为指令集体系结构不同。
但由于Linux和Windows运行在同一个CPU上,MOVE/ADD/...等机器操作应该是相同的。虽然我确实知道有 OS 特定的功能,例如打印到终端,但此功能可以由 stdio.h 的不同实现来提供,例如。而且,我仍然可以创建一个非常基本的程序,它只计算 a + b 而不打印任何内容,因此我不需要任何 OS-specific 代码。为什么我仍然需要为 Linux 和 Windows 编译,而不是仅仅向我的 Linux 可执行文件添加一个 .exe 扩展?
您可能想查看其他答案。
这是一个 问题 除了它是针对 C 而不是 C++
您可以在这里查看编译步骤的过程:
简而言之,尽管 C 可以跨平台到 运行,但由于编译器它不能跨平台编译。
除此之外,即使使用相同的指令,调用约定也可能不同,即参数在堆栈或寄存器中的位置、找到的顺序参数、在函数调用中必须保留哪些寄存器, return 值如何从被调用方传递给调用方。
虽然CPU一样,但还是有很多不同:
- 不同的可执行格式。
- 可能会使用不同的调用约定。例如 Windows x64 passes integer args in different registers than the x86-64 System V ABI 并且与其他 x86-64.
不同,Windows 中还有其他几个显着差异,包括调用保留 xmm6..15
- 关于堆栈结构的不同约定。一些系统有一个“red zone”的概念来帮助编译器生成更短的代码。执行环境必须尊重这样的概念以避免堆栈损坏。
- 程序link针对具有不同 ABI 的不同标准库编写 - 字段顺序可能不同,可能存在额外的扩展字段。
- 在 C 和 C++ 中,一些数据类型具有 OS 依赖大小。例如 x86_64
long
在 Linux 上是 8 个字节,但在 Windows 上是 4 个字节。 (类型大小和所需的对齐是构成 ABI 的另一部分,以及 struct/class 布局规则。)
- 标准库可以提供不同的函数集。在 Linux 上,libc 直接提供类似
snprintf
的函数,但在 Windows 上,snprintf
可能作为头文件中的 static inline
函数实现,实际上从 C 调用另一个函数运行时间。这对程序员来说是透明的,但会为可执行文件生成不同的导入列表。
- 程序以不同的方式与 OS 交互:在 Linux 上,程序可能会直接执行系统调用,因为它们已记录在案并且是提供的接口的一部分,而在 Windows 上它们没有记录,程序应该使用提供的功能。
- 即使两个 OS 依赖程序直接执行系统调用,每个内核都有自己的一组可用系统调用。
即使 Linux 程序只调用 C 库的包装函数,Windows C 库也不会有像 read()
这样的 POSIX 函数,ioctl()
,以及 mmap
。相反,Windows 程序可能会调用 VirtualAlloc
,这在 Linux 上不可用。 (但是使用 OS 特定系统调用的程序,而不仅仅是 ISO C/C++ 函数,即使在源代码级别也是不可移植的;它们需要 #ifdef
才能使用 Windows 系统调用仅在 Windows.)
- 与 OS 无关,但不同编译器编译的程序可能无法互操作:可能使用不同的标准库,C++ 名称重整之类的东西可能不同,因此无法 link 库相互之间,C++ 异常实现可能是不可互操作的。
- 不同的文件系统结构。 Windows 上的“”和类 Unix 上的“/”之间不仅存在差异,而且还有可能存在或不存在的“特殊文件”,如“/dev/null”。
理论上这里列出的所有问题都可以解决:可以编写自定义加载器以支持不同的可执行格式,如果整个程序使用同一套它们,不同的约定和接口不会造成问题。这就是为什么像 Wine 这样的项目可以 运行 Windows 二进制文件 Linux。问题是 Wine 必须在其他 OSes 提供的功能之上模拟 Windows NT 内核的功能,从而降低了实现效率。由于使用了不同的不可互操作的接口,此类程序在与本机程序交互时也存在问题。
在 Windows 模型之上模拟 fork()
等 POSIX 系统调用时,像 Cygwin 这样的源代码兼容层也可能效率低下。但总的来说,Cygwin 比 WINE 更容易:程序需要在 Cygwin 下重新编译。它不会尝试在 Windows.
下 运行 本机 Linux 二进制文件
这就像说,如果我使用相同的字母表,所有书籍都是相同的,生物学教科书和数学教科书是相同的,因为它们使用相同的字母表,有封面,有一些页面,等等。或者我必须滑雪胜地,因为它们都使用相同的字母表,并且因为它们都是关于雪的,所以它们的海报和小册子是相同的。
int main ( void )
{
return(27);
}
0000000000402cd0 <main>:
402cd0: 48 83 ec 28 sub [=10=]x28,%rsp
402cd4: e8 d7 e9 ff ff callq 4016b0 <__main>
402cd9: b8 1b 00 00 00 mov [=10=]x1b,%eax
402cde: 48 83 c4 28 add [=10=]x28,%rsp
402ce2: c3 retq
00000000004003e0 <main>:
4003e0: b8 1b 00 00 00 mov [=10=]x1b,%eax
4003e5: c3 retq
当然存在细微差别,但关键是这是两个完全不同的操作系统,即程序的 entry/exit(上面未显示的大量代码各不相同,而不仅仅是细小的规范如果此程序中的主要代码。
这些是不同的操作系统,它们有不同的调用不同的规则,它们是不同的,通用的指令集有些无关紧要。这就像说因为我在 linux 上 运行 并且使用 C 作为我的编程语言那么为 arm 制作的二进制文件和为 x86 制作的二进制文件应该是相同和兼容的(因为我说的三件事中的两个是相同的,编程语言和操作系统但不是指令集。或者在你的情况下是编程语言和指令集而不是操作系统。)
这甚至指出 windows 的 gcc 编译程序并不完全兼容 windows 的所有版本,您不能只说 "windows"。 linux 也是如此。它们独立于目标而在自身内部发生变化,然后操作系统之间存在不兼容的差异。仅仅因为砖和砂浆是一样的,并不能造就两座完全相同的建筑。
这是 JAVA 和 Python 等语言的目的,画一条线,这条线以上的一切都是通用的和跨平台的,这条线以下的可以是平台和目标特定的没有理由期待任何形式的跨平台兼容性。如果我们在具有 C 编译器的计算机或所有 运行 linux 独立于平台的计算机或所有 运行 具有编译器和相同的指令集。
下载某些程序(如 chrome 或 7-zip 或 firefox、handbrake 等)是有原因的,根据操作系统和操作系统,存在不同的安装程序 and/or 二进制文件版本。指令集通常甚至没有列出,因为它被假定为 x86,但有不同的二进制文件,如果它是如此微不足道,那么为什么那些已经交付成品这么长时间的人要交付产品的几个不同版本?
我已经了解了 CPUs/ASM/C 的基础知识,但不明白为什么我们需要为不同的 OS 目标编译不同的 C 代码。编译器所做的是创建汇编程序代码,然后将其汇编为二进制机器代码。 ASM 代码当然每个 CPU 体系结构(例如 ARM)不同,因为指令集体系结构不同。
但由于Linux和Windows运行在同一个CPU上,MOVE/ADD/...等机器操作应该是相同的。虽然我确实知道有 OS 特定的功能,例如打印到终端,但此功能可以由 stdio.h 的不同实现来提供,例如。而且,我仍然可以创建一个非常基本的程序,它只计算 a + b 而不打印任何内容,因此我不需要任何 OS-specific 代码。为什么我仍然需要为 Linux 和 Windows 编译,而不是仅仅向我的 Linux 可执行文件添加一个 .exe 扩展?
您可能想查看其他答案。
这是一个
您可以在这里查看编译步骤的过程:
简而言之,尽管 C 可以跨平台到 运行,但由于编译器它不能跨平台编译。
除此之外,即使使用相同的指令,调用约定也可能不同,即参数在堆栈或寄存器中的位置、找到的顺序参数、在函数调用中必须保留哪些寄存器, return 值如何从被调用方传递给调用方。
虽然CPU一样,但还是有很多不同:
- 不同的可执行格式。
- 可能会使用不同的调用约定。例如 Windows x64 passes integer args in different registers than the x86-64 System V ABI 并且与其他 x86-64. 不同,Windows 中还有其他几个显着差异,包括调用保留 xmm6..15
- 关于堆栈结构的不同约定。一些系统有一个“red zone”的概念来帮助编译器生成更短的代码。执行环境必须尊重这样的概念以避免堆栈损坏。
- 程序link针对具有不同 ABI 的不同标准库编写 - 字段顺序可能不同,可能存在额外的扩展字段。
- 在 C 和 C++ 中,一些数据类型具有 OS 依赖大小。例如 x86_64
long
在 Linux 上是 8 个字节,但在 Windows 上是 4 个字节。 (类型大小和所需的对齐是构成 ABI 的另一部分,以及 struct/class 布局规则。) - 标准库可以提供不同的函数集。在 Linux 上,libc 直接提供类似
snprintf
的函数,但在 Windows 上,snprintf
可能作为头文件中的static inline
函数实现,实际上从 C 调用另一个函数运行时间。这对程序员来说是透明的,但会为可执行文件生成不同的导入列表。 - 程序以不同的方式与 OS 交互:在 Linux 上,程序可能会直接执行系统调用,因为它们已记录在案并且是提供的接口的一部分,而在 Windows 上它们没有记录,程序应该使用提供的功能。
- 即使两个 OS 依赖程序直接执行系统调用,每个内核都有自己的一组可用系统调用。
即使 Linux 程序只调用 C 库的包装函数,Windows C 库也不会有像 read()
这样的 POSIX 函数,ioctl()
,以及 mmap
。相反,Windows 程序可能会调用 VirtualAlloc
,这在 Linux 上不可用。 (但是使用 OS 特定系统调用的程序,而不仅仅是 ISO C/C++ 函数,即使在源代码级别也是不可移植的;它们需要 #ifdef
才能使用 Windows 系统调用仅在 Windows.)
- 与 OS 无关,但不同编译器编译的程序可能无法互操作:可能使用不同的标准库,C++ 名称重整之类的东西可能不同,因此无法 link 库相互之间,C++ 异常实现可能是不可互操作的。
- 不同的文件系统结构。 Windows 上的“”和类 Unix 上的“/”之间不仅存在差异,而且还有可能存在或不存在的“特殊文件”,如“/dev/null”。
理论上这里列出的所有问题都可以解决:可以编写自定义加载器以支持不同的可执行格式,如果整个程序使用同一套它们,不同的约定和接口不会造成问题。这就是为什么像 Wine 这样的项目可以 运行 Windows 二进制文件 Linux。问题是 Wine 必须在其他 OSes 提供的功能之上模拟 Windows NT 内核的功能,从而降低了实现效率。由于使用了不同的不可互操作的接口,此类程序在与本机程序交互时也存在问题。
在 Windows 模型之上模拟 fork()
等 POSIX 系统调用时,像 Cygwin 这样的源代码兼容层也可能效率低下。但总的来说,Cygwin 比 WINE 更容易:程序需要在 Cygwin 下重新编译。它不会尝试在 Windows.
这就像说,如果我使用相同的字母表,所有书籍都是相同的,生物学教科书和数学教科书是相同的,因为它们使用相同的字母表,有封面,有一些页面,等等。或者我必须滑雪胜地,因为它们都使用相同的字母表,并且因为它们都是关于雪的,所以它们的海报和小册子是相同的。
int main ( void )
{
return(27);
}
0000000000402cd0 <main>:
402cd0: 48 83 ec 28 sub [=10=]x28,%rsp
402cd4: e8 d7 e9 ff ff callq 4016b0 <__main>
402cd9: b8 1b 00 00 00 mov [=10=]x1b,%eax
402cde: 48 83 c4 28 add [=10=]x28,%rsp
402ce2: c3 retq
00000000004003e0 <main>:
4003e0: b8 1b 00 00 00 mov [=10=]x1b,%eax
4003e5: c3 retq
当然存在细微差别,但关键是这是两个完全不同的操作系统,即程序的 entry/exit(上面未显示的大量代码各不相同,而不仅仅是细小的规范如果此程序中的主要代码。
这些是不同的操作系统,它们有不同的调用不同的规则,它们是不同的,通用的指令集有些无关紧要。这就像说因为我在 linux 上 运行 并且使用 C 作为我的编程语言那么为 arm 制作的二进制文件和为 x86 制作的二进制文件应该是相同和兼容的(因为我说的三件事中的两个是相同的,编程语言和操作系统但不是指令集。或者在你的情况下是编程语言和指令集而不是操作系统。)
这甚至指出 windows 的 gcc 编译程序并不完全兼容 windows 的所有版本,您不能只说 "windows"。 linux 也是如此。它们独立于目标而在自身内部发生变化,然后操作系统之间存在不兼容的差异。仅仅因为砖和砂浆是一样的,并不能造就两座完全相同的建筑。
这是 JAVA 和 Python 等语言的目的,画一条线,这条线以上的一切都是通用的和跨平台的,这条线以下的可以是平台和目标特定的没有理由期待任何形式的跨平台兼容性。如果我们在具有 C 编译器的计算机或所有 运行 linux 独立于平台的计算机或所有 运行 具有编译器和相同的指令集。
下载某些程序(如 chrome 或 7-zip 或 firefox、handbrake 等)是有原因的,根据操作系统和操作系统,存在不同的安装程序 and/or 二进制文件版本。指令集通常甚至没有列出,因为它被假定为 x86,但有不同的二进制文件,如果它是如此微不足道,那么为什么那些已经交付成品这么长时间的人要交付产品的几个不同版本?