Generally speaking, C programs are not "portable" meaning they can't be copied over to other machines and expected to run.
There are a few exceptional cases where C executables can safely be ran on other machines:
- The CPUs support the same instruction set, with the same set of compatible side-effects and possibly the same bugs.
- The Operating systems support the same system api points, with the same set of compatible side-effects and possibly the same bugs.
- The installed non-operating system libraries support the same api points, with the same set of compatible side-effects and possibly the same bugs.
- The API calling conventions are the same between the source (platform you built the code on) and destination (platform you will run the executable on).
Of course, not all of the CPU and OS need to be 100% compatible, just the parts your compiled program uses (which is not always easy to see as it is compiled, and not a 100% identical representation of the source code)
For these conditions to hold, typically you are using the same release of the operating system, or a compatibility interface designed by the operating system packagers that supports the current version of the operating system and older versions too.
The details on how this is most easily done differ between operating systems, and generally speaking, even if a compatibility layer is present, you need to do adequate testing as the side-effects and bugs tend to differ despite promises of multi-operating system compatibility.
Finally, there are some environments that can make non-same CPU executables run on an operating system (like QEmu) by simulating the foreign CPU instruction set at runtime, interperting those instructions into ones that are compatible with the current CPU. Such systems are not common across non-Linux operating systems; and, they may stumble if the loading of dynamic libraries can't locate and load foreign instruction set libraries.
With all of these caveats, you can see why most people decide to write portable source code and recompile it on each of the target platforms; or, write their code for an interpreter that already exists on multiple platforms. With the interpreter approach, the CPU is conceptually a virtual one, which is implemented to be identical across hardware, letting you write one set of source code to be interpreted across all the hardware platforms.