I once argued on the C standard forum that a C compiler should not know about "main". "#include <unix.h>" should contain the usual Unix declaration for "main", and "#include <windows.h>" should contain the Windows declaration, which at the time was, roughly:
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow);
It's then up to the user to define their startup function to match, with normal type checking.This gets the compiler out of handling "main" as a special case.
This was generally considered to be the right answer, but would break too much existing code.
but I agree that the fact that the standard allow for 2 different declarations of main in a language without poliformism doesn't help.
Not to start with the whole implementations are free to define extra entry points part.
The entry point is automatically generated by the compiler, it calls a few functions depending on what the program does then calls the main, I think it had to do with initializing the standard library. You can see the stub using a debugger or a disassembler.
It's possible to set the entry point point to any function name. See advanced project settings.
Now about the arguments and return type. With main the caller is responsible for pushing arguments onto the stack before the call, then popping the stack after the call. the return code is in the EAX register if I remember well.
Because of that, it doesn't matter what's the signature of the main, the invocation will work irrelevant of the arguments.
People may ask what's the point of knowing any of this? One major use case is to write executable compressors like UPX. Another use case is to make a custom entry point written in assembler.
Nevertheless, main is treated specially by the compiler for the aforementioned historical reasons, for example to not warn/error out if it does not return a value despite the type clearly telling so.
Observe:
% echo 'int main(void) { }' > foo.c; clang -c foo.c
<no output>
% echo 'int foo(void) { }' > foo.c; clang -c foo.c
foo.c:1:17: warning: non-void function does not return a value [-Wreturn-type]
int foo(void) { } ^ 1 warning generated.
As you can see, clang is happy to ignore the missing return value for main(), but not for foo().
You can pass -nostdlib to gcc to disable linking the CRT startup object (or use ld directly) and you can pass --default-script /dev/null to ld to disable the linker script.
There is no need to declare main or check arguments or return types since in C arguments are both pushed and popped by the caller and the language provides no typing guarantees and thus there is no problem in calling functions with mismatched argument or return type declarations.
$ gcc -m32 -O2 -fno-pie -fno-asynchronous-unwind-tables -fomit-frame-pointer -S -masm=intel -xc -o - -
int foo(void); int main(void) { return foo(); }
^D
.file "<stdin>"
.intel_syntax noprefix
.text
.section .text.startup,"ax",@progbits
.p2align 4
.globl main
.type main, @function
main:
push ebp
mov ebp, esp
and esp, -16
call foo
leave
ret
.size main, .-main
.ident "GCC: (GNU) 11.1.0"
.section .note.GNU-stack,"",@progbits
It doesn’t do that if you set the historical stack alignment, though (-mpreferred-stack-boundary=2), or if you name the function anything else but main (it even does a tail call). Presumably it’s trying to (somewhat) recover from the time when the GCC authors accidentally the SysV i386 ABI[1,2].[1]: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=40838 [2]: https://stackoverflow.com/a/49397524
So there's the utility, if you're hardcore enough to build machine code at runtime.
If you wanted to abuse main() particularly, I guess you've got argc and argv in registers, and your hand-compiled main 'function' could maybe have some self-modifying code?