I just looked on my machine (which is running macOS Sierra), and I found four runtime systems for C and C++ alone:
/usr/lib/crt1.10.5.o
/usr/lib/crt1.10.6.o
/usr/lib/crt1.o
/usr/lib/gcrt1.o
They're not huge; only about 10kB each. Small enough that if you statically linked them to a typical program they would barely register. Still, there they are.
I'm certain that I have a bunch of others for different languages, too: Python and Java are fairly standard. Emacs seems to come pre-installed so there's a Lisp-like language. There's probably a Swift runtime if I look hard enough. And, of course, that's not including all the languages I installed myself because I like collecting programming languages.
Many modern operating systems are designed with C in mind (in that their system call APIs are C calls), and many languages with a "foreign function" interface support C natively because so many third-party libraries support a C API. So runtimes for many languages are often built on top of the C runtime.
It's useful to think of a runtime system as some kind of virtual machine. However, many programming languages do compile to machine code, so the job that the runtime system has to do can be as simple as initialisation and shutdown only, with nothing further needed while the program is running.
For example, a typical runtime for C and C++ (they are often shared these days) has to perform the following initialisation before any C code is run:
- Set up the C stack.
- Initialise any standard library data structures that need to be initialised (e.g.
atexit
or stdin
/stdout
/stderr
).
- Invoke static constructors (e.g. for C++).
- Process command-line arguments and environment variables to pass to
main()
.
- Call
main()
.
There's nothing here that really corresponds to interpreting the output of the compiler, with the possible exception of information that the compiler generates about static constructors.
Even for compiled languages, however, the runtime may provide subroutines to support source-level operations for which it would be wasteful or inconvenient for the compiler to generate code.
Exceptions in C++ are one example. Another is where some particular platform doesn't have a suitable instruction; many 32-bit platforms don't have a native 64-bit division instruction, but the compiler may still want to support 64-bit integers.