5 ms·
Is this something that the compiler normally handles or is it handled by the runtime environment upon executing a program?
by mywittyname 6y ago
Is this something that the compiler normally handles or is it handled by the runtime environment upon executing a program?
- chrisseaton 6y agoThey're part of the language specification - the main function has different semantics to a normal function in a few ways - so the compiler must handle it to some extent.
- oso2k 6y agoNot true. The kernel & shell makes the values available on the stack and the runtime can choose to link to them or not. The ABI also details how and where the values should be placed.
- chrisseaton 6y ago> Not true. 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.
- oso2k 6y agoThis is the C Standard specifying its VM to match the assumptions of a *nix environment. `main` isn't so special, IMO. Conformance to the C Standard is always optional, see C++, golang, Rust. However, conformance to ABI is less optional.
- kps 6y ago> You need compiler support for it. 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.
- chrisseaton 6y agoNow try 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.
- kps 6y agoDamn, you're right: Currently, “reaching the } that terminates the main function returns a value of 0”. In my defense, that was different in C89, which was the last one I worked on implementing and knew completely: “If the main function executes a return that specifies no value, the termination status returned to the host environment is undefined” and “Reaching the } that terminates a function is equivalent to executing a return statement without an expression.”
- saagarjha 6y agoA fun fact related to the other points: you can legally call main in C, but not in C++ (it will usually work, though). Another fun fact is that on some platforms (Linux, macOS) you can "dynamically load" a (dynamically linked) program by calling dlopen on it and using dlsym to grab the address of main and just calling it :)
- kps 6y ago> you can legally call main in C 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.
- wruza 6y agoTechnically, local C ABI may be callee-cleanup, and then main() must be special in that it's neither variadic nor fixed-prototype. But in a major compatible reality it's always cdecl, thus not much different from foo(void) being called with foo(int, ptr, ptr) via a prototype mismatch which a compiler is surely unable to detect (that's UB for that exact non-existent ABI reason, as it is for almost half of C). It is only special for a return statement really.
- oso2k 6y agoLocating argc, argv, and envp on the stack is done by the runtime [0]. [0] https://github.com/lpsantil/rt0/blob/master/src/lib/00_start.c https://github.com/lpsantil/rt0/blob/master/src/lib/00_start...
- jcranmer 6y agoThis is actually part of the C library. If you compile a simple C file, the final link step looks like this: "/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.
- xandris 6y agoAt least on the toolchains I'm familiar with, the linker will insert crt0.o, a very basic part of the C runtime, which contains a _start implementation that pulls argc, argv, and envp from the stack: https://en.wikipedia.org/wiki/Crt0 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.
- saagarjha 6y agoInterestingly, macOS doesn't provide a crt0.o: execution begins in dyld for most programs, which calls the normal program entrypoint (usually main). Linux will do this too if you are dynamically linked.