我会把你的问题分解成更小的子集
我在 GCC 中编译了一个小型“Hello World”程序,它在 Linux 中运行。
这是意料之中的,因为系统上安装的常用 GCC 和 binutils 被配置为在执行它们时针对相同的目标和主机进行编译。或者用外行的话来说,它们将生成一个适用于 Linux 内核和 GNU C 标准库(或兼容的 libc)运行时的可执行文件。
也有编译器可以构建程序以在不同类型的操作系统和/或 CPU 架构上运行,而不是它们本身在其上执行。这些就是所谓的交叉编译器,例如,您正在使用它们在您的台式计算机上构建智能手机应用程序。当您使用 Windows Android NDK 创建本机 Android 可执行文件时,实际上是针对 Linux 内核和不同的 CPU(很可能是 ARM 或 MIPS)进行交叉编译。
然后我在程序中添加了 .exe 扩展名,看看它是否适用于 windows。
在这里,您陷入了一个误解,我永远无法理解它为什么会出现在首位。文件名后缀必须与它可以做的任何事情有关。 Windows 使用它在注册表中查找,如何处理它,但仅此而已。在 Windows 中,您可以注册任何您希望被识别为“可执行”的后缀。 Linux(作为 Unixoid)做了一些更简单的事情,但恕我直言,它更强大:每个文件都带有一组标志,如果它可以执行,如果可以,那么谁可以执行。
但是要作为一个可以运行的程序对系统真正有用,需要一些不同的东西,并且所有可执行文件(对于给定的操作系统)共享,无论它们的文件名如何:一种特定的内部格式。 Windows 使用所谓的PE 格式(PE 代表可移植可执行文件,这有点可笑,因为唯一支持它的操作系统是 Windows)。 Linux 和 *BSD 使用 ELF(可执行和链接格式),MacOS X 使用称为 Mach 二进制格式 的东西,向 Intel CPU 的过渡扩展到 Mach 通用二进制 格式.从技术上讲,Mach 内核(MacOS X 的基础)也可以理解 ELF 格式,但在 MacOS X 中没有启用 AFAIK。
虽然这两种文件格式本质上是做同样的事情(描述如何将文件加载到内存中、它依赖于哪些库、必须加载哪些库、加载它们的位置、实际启动程序的哪个点),但它们的结构非常不同。 Windows 不知道如何加载和执行 ELF,Linux 不知道如何加载和执行 PE。
即使其中一方支持另一方,系统仍将缺少程序期望加载和运行的库。至少您需要(操作系统)相关的运行时环境,它负责从操作系统检索启动参数等“平凡”的事情,为操作系统特定的内存管理功能提供接口等等。这些也从根本上说Linux 和 Windows 的区别。
请注意,完全有可能提供一个兼容性包装器,它建立在操作系统特定的东西之上,将其他操作系统模拟到程序中,并且还知道如何加载、链接和启动外部可执行文件。在 Linux 上加载和执行 Windows 程序的包装器称为 WINE,也存在称为 LINE 的相反的东西,它在 Windows 上运行 Linux 可执行文件。
但它没有工作。程序在windows中无法运行的原因是什么。
Windows 与 Linux 不同。它有一个完全不同的系统级 API,它使用不同的二进制格式,甚至像标准输入/标准输出(由 C 标准库定义)这样的基本功能也有很大不同的实现方式。哎呀,他们甚至使用不同的callingconventions
Linux 使用标准的 C 调用约定 (stdcall)。 Windows 主要使用 cdecl 约定,它是 pascal 和 stdcall 约定的混合体。
基本上二进制和汇编指令在 windows 和 linux 下编译的程序中是相同的(如果不使用操作系统特定的库)。
呃,不,他们绝对不是。调用约定的不同会产生不同的程序集。
操作系统只需将程序加载到 RAM 中,处理器就会执行它。
事情没那么简单。该程序还必须“连接”到操作系统,才能做任何有用的事情。您想在控制台上看到打印的字符串吗?然后你必须将它连接到一些操作系统功能,这样做。
在 Linux 中,控制台已经存在,并且有特殊文件 /dev/console、/dev/stdin、/dev/stdout、/dev/stderr,它们在内部与当前的 pts 或 tty 相关联。在进程中,它们总是映射到文件描述符 0、1 和 2。
在 Windows 中,程序可能需要也可能不需要打开自己的控制台窗口(如果它是从 CLI 启动的,它将使用它)。所以它必须调用AllocConsole,如果失败作为后备AttachConsole(parent_PID);然后它可以使用GetStdHandle 来获取标准输入、标准输出、标准错误。这个得到的HANDLEs是任意的,需要连接到C stdio(printf、fwrite等)和C++ iostream的内部实现。
CPU 也不会“神奇地”执行一段二进制文件,只是因为它最终位于 RAM 中的某个位置。操作系统必须创建一个新进程,并且要做到这一点,它必须知道二进制某些重要代码所在的位置(入口点)。这些入口点(除其他外)在二进制文件格式的特殊位置进行描述。正如我已经解释过的,Windows 只理解一种与 Linux 完全不同的可执行二进制格式 (PE)。一旦二进制文件被加载和链接,它就必须真正启动。如上所述,在 Linux 和 Windows 之间启动二进制文件的步骤也不同。
后期编辑,但必须写:
*此外,了解可执行文件的二进制文件在程序启动时根本没有完全加载到 RAM 中是非常重要的。所发生的是,创建了所谓的“虚拟内存映射”。这意味着一个虚拟地址空间是由操作系统创建的,与其他一些数据结构一起这就是通常所说的进程。虚拟地址空间又由一组所谓的“页面”支持,这些页面是映射到实际内存或存储设备的地址空间块。事实上,通常 RAM总是用作磁盘操作的缓存。当程序二进制文件被映射时,虚拟内存页面将始终指向磁盘存储,而 RAM 仅充当缓存。动态分配的内存取自在存储设备上缓存交换空间的磁盘缓存。如果没有交换设备,那么它只是从通用磁盘缓存中获取。*
如您所见,这些是要执行的任务的完全不同的列表。再加上调用约定和二进制格式的差异。
那么,为什么它不起作用。
因为可执行文件不仅仅是惰性汇编指令。程序需要与操作系统对话才能产生有意义的东西。而且 Windows 和 Linux 非常非常不同。