https://en.wikipedia.org/wiki/Crt0
So it's not really part of the "compiler" itself, it's usually an object file the linker includes in the linking job on your behalf.
[0] https://github.com/lpsantil/rt0/blob/master/src/lib/00_start...
"/usr/bin/ld" --hash-style=both --build-id --eh-frame-hdr -m elf_x86_64 -dynamic-linker /lib64/ld-linux-x86-64.so.2 -o a.out /usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/crt1.o /usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/crti.o /usr/bin/../lib/gcc/x86_64-linux-gnu/9/crtbegin.o -L/usr/bin/../lib/gcc/x86_64-linux-gnu/9 -L/usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu -L/lib/x86_64-linux-gnu -L/lib/../lib64 -L/usr/lib/x86_64-linux-gnu -L/usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../.. -L/usr/lib/llvm-9/bin/../lib -L/lib -L/usr/lib /tmp/test-4e14b0.o -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/bin/../lib/gcc/x86_64-linux-gnu/9/crtend.o /usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/crtn.o
Breaking it out in pieces, your code comes from:* /usr/lib/x86_64-linux-gnu/crt1.o -- This file contains the "beginning" of your executable, and the definition of _start (the entry point to your entire application).
* /usr/lib/x86_64-linux-gnu/crti.o -- This contains the beginning of the .init and .fini instructions, which include the glue code for calling static initializers and destructors. This is more specifically linker magic to trigger ELF magic headers.
* /usr/lib/gcc/x86_64-linux-gnu/9/crtbegin.o -- More glue code for static initializers and destructors. There are a few different approaches to implementing static initializers that varies on a per-compiler basis.
* /tmp/test-4e14b0.o -- Your code! When you specify multiple object files, or multiple libraries, all of those links get dropped in this location.
* -lgcc, -lgcc_s -- These are compiler runtime libraries to handle supporting native C/C++ operations that are not implemented in hardware. For example, should you do a 64-bit division operation on an architecture that doesn't have such an instruction, the compiler generates a call to a library function implemented in these libraries.
* -lc -- This is the C runtime library. Here you may find the implementation of printf, for example.
* -lgcc, -lgcc_s (again!) -- The C runtime library's implementation may need to call out to these compiler-generated function calls, so include the libraries again in the search path because legacy linker order means you have to.
* /usr/lib/gcc/x86_64-linux-gnu/9/crtend.o -- This is the counterpart to crtbegin.o. Whereas crtbegin.o includes some stuff that will happen before all the user code is included, this includes some stuff that will happen afterwards.
* /usr/lib/x86_64-linux-gnu/crtn.o -- Ditto, but for crti.o instead.
On a modern glibc system, the linker will arrange for the entry point to be included in _start, defined in crt1.o. The Linux ABI formally specifies how the register state and stack will be arranged when you load a program image, and _start will translate this ABI into a function call invocation to __libc_start_main. __libc_start_main is implemented in libc.so, and will handle calling global initializers and the like before calling the main function implemented in your program.
In practice, most modern executables are dynamic executables, which means that executing your program means the Linux kernel actually executes another program instead (/lib64/ld-linux-x86-64.so.2 for x86-64), which will take on the burden of loading your program and then calling _start itself. This is to support loading shared libraries (such as the C library!) and implementing relocations, which the Linux kernel does not know how to do.
All of this tends to fall under the purview of linking and loading, which is generally not handled by compiler writers. Even most new programming languages tend to find it easier to link their programs using your platform's C compiler because it's nasty to worry about all of these details yourself.
Yes true. Check 5.1.2.2.1 and 5.1.2.2.3 in the standard.
If you don't treat 'main' specially you won't have a conforming C compiler. It's unique in accepting different signatures, and for its return value syntax and semantics.
You cannot just compile it as a normal function and link it with a runtime routine. You need compiler support for it.
Good point. That makes it relatively difficult (not impossible) to compile `main` differently from other functions, other than diagnosing bad signatures if that's what the hosted environment requires, since it has to be callable like other functions.
There are probably implementations that require compiler support, but it is not required by the standard and not true of many common implementations. Support is by the caller, as described in others' parallel comments.
> You cannot just compile it as a normal function and link it with a runtime routine.
You can. As a concrete example, for the following two programs,
int maim(int argc, char *argv[]) { return 0; }
int main(int argc, char *argv[]) { return 0; }
the assembly code produced by gcc 9.3 x64 is identical except for the spelling of the identifier. int maim(int argc, char *argv[]) { }
int main(int argc, char *argv[]) { }
They compile to different code. Can you figure out why?A function called main is treated differently in this regard to other functions, which is enforced by the standard, and so has to be implemented by the compiler.