4 Answers
4

As a program can only contain one function called main, it is actually very simple for the compiler to support the multiple signatures.

In the call-sequence for main, the caller is made responsible for cleaning up any arguments that were provided.
And the compiler provides a piece of startup code that prepares the arguments for main and then invokes the one function of that name, without checking if it will actually use those arguments.

As a side note, the last two prototypes are identical and in a function definition, the first two would be identical as well. That leaves only two supported signatures for main.

@All: Yeah, I do realize that there are actually only two distinct prototypes among the ones I posted. Actually, while looking for the answer, I pasted those as they were from Wikipedia (my mistake!). Will edit now. Thanks all!
–
high5Mar 2 '13 at 9:58

The compiler supports multiple prototypes by not having one at all. See the reference to the C standard in my answer.
–
BlrflMar 2 '13 at 13:50

@Blrfl: You are right that the implementation does not provide any prototype for main. I used the term 'prototype' a bit more loosely to mean the signature of a function.
–
Bart van Ingen SchenauMar 2 '13 at 14:44

C does support overloading, it doesn't support user defined overloading of functions but operators have a fixed set of standard defined overloads.

how are multiple prototypes of main () supported?

The compiler has to do the right things for different possible definitions of main(), but it has just to keep one prototype for recurse call checks; it hasn't to behave differently for main than for any other function excepted when compiling its definition. You can't provide yourself a prototype which won't match the definition you use and expect things behave sanely.

BTW, note that has your two last lines are strictly equivalent and the difference between the two first may occurs in valid programs for other functions than main.

One thing you have to understand about C or any other language that produces native object files is that the only time function prototypes matter is during compilation. By the time the linker sees it, the only thing to be done is fill in the missing addresses of external functions.

Arranging the passing of function parameters is entirely up to the compilers, usually based on an architecture-specific convention. Most implementations on machines with stacks push the parameters in reverse order followed by the return address. Ergo, when code wants to call f(int a1, int a2, int a3), the stack would contain Return Addressa1a2a3 if you were reading it from the top down. This means the function can get at a1 by looking at the second item in the stack, a2 by looking at the third, etc.

Calling main() works exactly the same way, so from its perspective, the stack will contain Return Addressargcargvenvp. (That last item is something Unix and other systems have done for decades, but it's not in any standard. You'll see why this doesn't matter in a second.)

Section 5.1.2.2.1 of the C standard specifically says that no implementation will define a prototype for main() and that the two standard implementations are main() and main(int argc, char **argv). Not having a pre-defined prototype allows you to declare main any way you like and not have the compiler balk at it. A zero-argument version will ignore the stack entirely and the two-argument flavor will look at the second and third items. You could also, do a one-argument version, main(int argc), which would work because it would consume argc and not know that argv is there.

As you might have guessed, this trick only works in the direction of a caller calling a function with more arguments than the called function consumes will just have pushed on more than the caller will see. A called function with more parameters than the caller pushed will simply look further down the stack than it should and fall victim to the GIGO principle. Since the caller undoes the adjustment to the stack pointer, the stack comes out of it unscathed after the called function returns.

Addendum, addressing Bart van Ingen Schenau's comment:

C++ mangles its symbol names to prevent collisions between same-named, differently-prototyped functions, as almost every native object file format on the planet differentiates symbols only by name. That has the pleasant side effect of preventing mismatched calls between C++ object files, but only because a C++ compiler compiling a wrongly-prototyped call will emit a symbol that won't be present in the object file that would contain the called function. It happens that you get human-readable C++ function names in linker error messages because many of them recognize and demangle C++ symbols.

An object file can refer to a C++ function in another object file by its mangled name and attempt to call it. (I've actually done this experimentally and would never in a million years condone doing it in "real" code.) The linker, which has no idea whether either object was produced by a C++ compiler or from some bit of hand-written assembly, will do nothing to prevent it as long as the symbol names match. That makes the assertion that the linker plays any direct role in enforcing C++ language policies erroneous.

Your first paragraph is incorrect. For languages like C++, number and type of the arguments gets encoded in the symbol names, so the information contained in a prototype is also relevant at the linking stage then.
–
Bart van Ingen SchenauMar 2 '13 at 14:41

@BartvanIngenSchenau: What I said is technically correct. See my addendum for an explanation.
–
BlrflMar 2 '13 at 16:02

Similarly, you're defining main yourself; the implementation doesn't define it for you. What's different about main is not that it can have one of two different prototypes, it's that it's restricted to have one of only those two different prototypes (or equivalent). And that's to ensure that the compiler and the calling environment know how to invoke yourmain function. (For foo, any calls will be your own.)

(main may also be defined in some other implementation-defined form, but such forms are not portable.)

As for why those two are permitted, rather than, say, requiring exactly the second form, it's mostly for historical reasons. Early C compilers didn't have prototypes (they were introduced in the 1989 C standard, borrowed from C++). In pre-standard C, defining

main() { /* ... */ }

probably just meant that the calling environment would pass argc and argv values (and possibly envp) to main, and main would simply ignore them. It was convenient to omit the definitions of argc and argv if your program wasn't going to use them. Calling conventions were such that the two forms were effectively compatible.

For historical reasons, modern calling conventions are likely to work similarly, but since the 1989 ANSI standard compilers have been permitted to make the two forms incompatible -- but if they are, then the implementation has to do whatever is necessary to make both forms work.