25

If I write a C program and compile it to an .exe file, the .exe file contains raw machine instructions to the CPU. (I think).

If so, how is it possible for me to run the compiled file on any computer that runs a modern version of Windows? Each family of CPUs has a different instruction set. So how come any computer that runs the appropriate OS can understand the instructions in my .exe file, regardless of it's physical CPU?

Also, often in websites in the "download" page of some application, you have a download for Windows, for Linux, and for Mac (often two downloads for each OS, for 86 and 64 bit computers). Why aren't there many more downloads, for each family of CPUs?

5
  • 4
    CPUs have standard like x86, 64b, etc.. executables DO depend on the CPU. You can't run your exe you mentioned on some special CPU like RISC, there are plethora of special purpose CPU as well
    – InformedA
    Commented Jul 26, 2014 at 10:40
  • OSs are made up of executables, and they depend on the CPU, not on OSs, because they are the OS. Commented Jul 27, 2014 at 0:47
  • Because of backward compatibility.
    – MiKL
    Commented Jan 9, 2017 at 13:20
  • 3
    often two downloads for each OS, for 86 and 64 bit computers : I interpret that as a dependency of executables on CPUs.
    – mouviciel
    Commented Jan 9, 2017 at 14:44
  • I think the OP's point is: Are the CPU instructions which differ across CPUs, ever used, if a program using them would not run on the other CPUs? For instance, XOP Commented Feb 14, 2022 at 14:23

3 Answers 3

47

Executables do depend on both the OS and the CPU:

  • Instruction Set: The binary instructions in the executable are decoded by the CPU according to some instruction set. Most consumer CPUs support the x86 (“32bit”) and/or AMD64 (“64bit”) instruction sets. A program can be compiled for either of these instruction sets, but not both. There are extensions to these instruction sets; support for these can be queried at runtime. Such extensions offer SIMD support, for example. Optimizing compilers might try to take advantage of these extensions if they are present, but usually also offer a code path that works without any extensions.

  • Binary Format: The executable has to conform to a certain binary format, which allows the operating system to correctly load, initialize, and start the program. Windows mainly uses the Portable Executable format, while Linux uses ELF.

  • System APIs: The program may be using libraries, which have to be present on the executing system. If a program uses functions from Windows APIs, it can't be run on Linux. In the Unix world, the central operating system APIs have been standardized to POSIX: a program using only the POSIX functions will be able to run on any conformant Unix system, such as Mac OS X and Solaris.

So if two systems offers the same system APIs and libraries, run on the same instruction set, and use the same binary format, then a program compiled for one system will also run on the other.

However, there are ways to achieve more compatibility:

  • Systems running on the AMD64 instruction set will commonly also run x86 executables. The binary format indicates which mode to run. Handling both 32bit and 64bit programs requires additional effort by the operating system.

  • Some binary formats allow a file to contain multiple versions of a program, compiled for different instruction sets. Such “fat binaries” were encouraged by Apple while they transitioning from the PowerPC architecture to x86.

  • Some programs are not compiled to machine code, but to some intermediate representation. This is then translated on-the-fly to actual instructions, or might be interpreted. This makes a program independent from the specific architecture. Such a strategy was used on the UCSD p-System.

  • One operating system can support multiple binary formats. Windows is quite backwards compatible and still supports formats from the DOS era. On Linux, Wine allows the Windows formats to be loaded.

  • The APIs of one operating system can be reimplemented for another host OS. On Windows, Cygwin and the POSIX subsystem can be used to get a (mostly) POSIX-compliant environment. On Linux, Wine reimplements many of the Windows APIs.

  • Cross-platform libraries allow a program to be independent of the OS APIs. Many programming languages have standard libraries that try to achieve this, e.g. Java and C.

  • An emulator simulates a different system by parsing the foreign binary format, interpreting the instructions, and offering a reimplementation of all required APIs. Emulators are commonly used to run old Nitendo games on a modern PC.

2
  • 12
    You should mention that both java and .net are examples of using an intermediate format -- intermediate formats are very popular today, and not just a relic of a 1970's system that ran off of 5 1/4 floppies.
    – jmoreno
    Commented Jul 26, 2014 at 16:31
  • @AKoscianski thank you for your suggested edit. However, I think the security design isn't the reason why executables are OS-dependent, but the reason why we have the OS vs. userland split in the first place. The different APIs to such protected operating system functionality were already addressed by the “System APIs” section of this answer.
    – amon
    Commented Apr 23, 2017 at 10:34
4

99% of current PCs running Windows have a 64 bit processor, which also is capable of running 32 bit software. The other one percent has 32 bit processors. So software built for 32 bit processors runs everywhere. Software built for 64 bit processors runs on every PC that the creator of the software cares about.

MacOS X and iOS support "fat binaries" - what you download actually can contain versions for different processors. Nobody builds applications for PowerPC processors anymore, but at some point a few years ago an executable could contain a PowerPC, a 32 bit Intel, and a 64 bit Intel version, and the right one would be executed. On iOS nowadays, when you download an app, you will get a version suitable for the processor on your device. Download on a different device, and you get a different version. Totally invisible to the user.

1

An exe contains more information than just raw machine code. The OS reads this when loading it and can figure out how it should run.

When you compile you generally set a target CPU, if you don't the compiler will pick your current CPU and will restrict itself to only choosing instructions that are common to your CPU and older versions of it. If you want to use a fancy new instruction that's specific to certain revision of your target CPU, you can either tell the compiler or manually code it with either intrinsics or inline assembly code. However your program will crash if it's run on a CPU that doesn't support that instruction.

Not the answer you're looking for? Browse other questions tagged or ask your own question.