As far as I know,
main ()
function has the following prototypes:
int main();
int main(int argc, char **argv);
Now, C does not support overloading, then how are multiple prototypes of
main ()
supported?
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
.
3
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 Address
a1
a2
a3
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 Address
argc
argv
envp
. (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.
3
If you define a function named foo
, you can give it any (legal) prototype you like:
int foo(void);
int foo(int argc, char *argv[]);
void foo(long double this, const unsigned short ***that, struct thingie the_other);
You’re defining foo
yourself, so you can define it any way you like.
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 your main
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.
C does not support overloading
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.