Why Is the Type of the Main Function in C and C++ Left to the User to Define

What these C's main function indicates?

There are two forms of conforming implementations specified by the c standard:

  • Hosted Implementation &
  • Freestanding Implementation

There are based on two types of envrionments that the c standard defines as:

  • Hosted environment &
  • Freestanding environment respectively.

What is freestanding Environment & What is Hosted Environment?

A freestanding implementation is one that is designed for programs that are executed without the benefit of an operating system.
For Ex: An OS kernel or Embedded environment would be a freestanding environment.

A program using the facilities of an operating system would normally be in a hosted implementation.

How does a c program execute in these two environments? What is the difference?

How a C program begins execution in both these environment differs.

For an Freestanding environment, the program startup can happen by any implementation defined function. There is no requirement that even a main() should exist.

So any of the functions definitions mentioned in the question can be valid depending upon implementation for that Freestanding Environment. And their function parameters and return values will have implementation defined meaning, So you will need to check their documentation to know their precise meanings.

Reference:

5.1.2.1 Freestanding environment

In a freestanding environment (in which C program execution may take place without any
benefit of an operating system), the name and type of the function called at program
startup are implementation-defined
. Any library facilities available to a freestanding
program, other than the minimal set required by clause 4, are implementation-defined.

For an Hosted environment the standard mandates the program execution begins by execution of a main() function and it also mandates how this function will be defined.

The specifications for the same are given in:

C99 Standard: 5.1.2.2 Hosted environment

5.1.2.2.1 Program startup

1 The function called at program startup is named main. The implementation declares no
prototype for this function. It shall be defined with a return type of int and with no
parameters:

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

or with two parameters (referred to here as argc and argv, though any names may be
used, as they are local to the function in which they are declared):

   int main(int argc, char *argv[]) { /* ... */ }

or equivalent; or in some other implementation-defined manner.

Why so many different types of writing main() in C

You need input arguments if you want your program to get some data from the command line. For example, you can run smth like this in the Command Prompt

./test arg1 arg2

In you have int main( int argc, char* argv[] )declared you'll get argc = 3 and argv is an array of chars representing each element {test, arg1, arg2}.

If you don't want you program to get any user console input just declare it as int main( ).

Think of main as an entry point for your program, the point from where execution takes place. On some embedded platforms you can name your entry point whatever you like (i.e. int superPuperMain() ).

The return code of main is treated when you launch your program from some script and you are interested in the return code of it. Think of your program as a function with return code here. Of course it's not correct but it gives you a general feeling about it :).

Why would you precede the main() function in C with a data type?

The following has been valid in C89

main() {
return 0;
}

But in modern C (C99), this isn't allowed anymore because you need to explicitly tell the type of variables and return type of functions, so it becomes

int main() {
return 0;
}

Also, it's legal to omit the return 0 in modern C, so it is legal to write

int main() {

}

And the behavior is as if it returned 0.

People put void between the parentheses because it ensures proper typechecking for function calls. An empty set of parentheses in C mean that no information about the amount and type of the parameters are exposed outside of the function, and the caller has to exactly know these.

void f();
/* defined as void f(int a) { } later in a different unit */

int main() {
f("foo");
}

The call to f causes undefined behavior, because the compiler can't verify the type of the argument against what f expects in the other modules. If you were to write it with void or with int, the compiler would know

void f(int); /* only int arguments accepted! */

int main(void) {
f("foo"); /* 'char*' isn't 'int'! */
}

So for main it's just a good habit to put void there since it's good to do it elsewhere. In C you are allowed to recursively call main in which case such differences may even matter.

Sadly, only a few compilers support modern C, so on many C compilers you may still get warnings for using modern C features.

Also, you may see programs to declare main to return different things than int. Such programs can do that if they use a freestanding C implementation. Such C implementations do not impose any restrictions on main since they don't even know or require such a function in the first place. But to have a common and portable interface to the program's entry point, the C specification requires strictly conforming programs to declare main with return type int, and require hosted C implementations to accept such programs.

What should main() return in C and C++?

The return value for main indicates how the program exited. Normal exit is represented by a 0 return value from main. Abnormal exit is signaled by a non-zero return, but there is no standard for how non-zero codes are interpreted. As noted by others, void main() is prohibited by the C++ standard and should not be used. The valid C++ main signatures are:

int main()

and

int main(int argc, char* argv[])

which is equivalent to

int main(int argc, char** argv)

It is also worth noting that in C++, int main() can be left without a return-statement, at which point it defaults to returning 0. This is also true with a C99 program. Whether return 0; should be omitted or not is open to debate. The range of valid C program main signatures is much greater.

Efficiency is not an issue with the main function. It can only be entered and left once (marking the program's start and termination) according to the C++ standard. For C, re-entering main() is allowed, but should be avoided.

Function type of main()

The C11 standard lists two kinds of environments: freestanding environment, meaning an embedded system or operative system, and hosted enviroment, meaning a program running on top of an OS.

5.1.2.1 Freestanding environment

In a freestanding environment (in which C program execution may take
place without any benefit of an operating system), the name and type
of the function called at program startup are implementation-defined.

In other words, in freestanding environments, the function called at startup could be called anything, and have any form. Most common is void main (void).

From C11, the chapter regarding hosted environment:

5.1.2.2.1 Program startup

The function called at program startup is named main. The
implementation declares no prototype for this function.

The "implementation" means the compiler, so the compiler declares no such function. It is up to the user (programmer) to do so. This can be done in the form int main (void) or int main(int argc, char *argv[]) or in any implementation-defined way specified by the compiler. In any case, the function is defined by the user.

C++ is a bit stricter and enforces any of the two forms, and allows no implementation-defined version of main. From C++03 3.6.1:

An implementation shall not predefine the main function. This function
shall not be overloaded. It shall have a return type of type int, but
otherwise its type is implementation-defined. All implementations
shall allow both of the following definitions of main:

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

and

int main(int argc, char* argv[]) { /* ... */ }


Regarding whether main can be called or not: I don't believe there is anything in the C standard preventing this, even though calling main makes no sense whatsoever. Since it has no prototype, the only way to call it would be recursively, which is just a plain stupid thing to do, but quite possible.

In C++, calling main() was explicitly banned from C++03 and later standards:

The function main shall not be used (3.2) within a program. The
linkage (3.5) of main is implementation-defined. A program that
declares main to be inline or static is ill-formed.

Why is main necessary in a program

Why? Because the standard says so (mostly).

The main function is required for hosted C environments (freestanding environments are allowed to start up any way they like).

If you're developing a library, you don't need a main for the library itself but you won't be able to turn it into an executable without one (other than by using non-portable trickery). And, at a bare minimum, you should have one for the test suite anyway.

In other words, your library should have a large test suite which is controlled from a main function (most likely in a separate source file or files) so that you can test any new work and regression-test to ensure it hasn't stuffed up the old work.

Why use define keyword to define a function

Because otherwise you'd have a source code that looks like this:

void do_add (bigint_stack &stack) {
bigint right = stack.front();
stack.pop_front();
TRACE ('+', "right = " << right);
bigint left = stack.front();
stack.pop_front();
TRACE ('+', "left = " << left);
bigint result = left + (right);
TRACE ('+', "result = " << result);
stack.push_front (result);
}

void do_subtract (bigint_stack &stack) {
bigint right = stack.front();
stack.pop_front();
TRACE ('-', "right = " << right);
bigint left = stack.front();
stack.pop_front();
TRACE ('-', "left = " << left);
bigint result = left - (right);
TRACE ('-', "result = " << result);
stack.push_front (result);
}

Etcetera...

Now, if you want to add another TRACE, for example, you'd have to again copy-paste it to all of them.

What the author wanted, really, is to define a way to generate functions from a set of parameterized inputs, in such a way that the resulting functions are all similar but they behave in slightly different ways depending on the input given to generate them. It's called meta-programming. Very common in coding parsers, which I suspect is where this snippet came from.

Now, in other languages, a construct specific to that language might exist to do meta-programming like this in a cleaner way (templates, metaclass, etc). But for C, macro is it.

How does the main() method work in C?

Some of the features of the C language started out as hacks which just happened to work.

Multiple signatures for main, as well as variable-length argument lists, is one of those features.

Programmers noticed that they can pass extra arguments to a function, and nothing bad happens with their given compiler.

This is the case if the calling conventions are such that:

  1. The calling function cleans up the arguments.
  2. The leftmost arguments are closer to the top of the stack, or to the base of the stack frame, so that spurious arguments do not invalidate the addressing.

One set of calling conventions which obeys these rules is stack-based parameter passing whereby the caller pops the arguments, and they are pushed right to left:

 ;; pseudo-assembly-language
;; main(argc, argv, envp); call

push envp ;; rightmost argument
push argv ;;
push argc ;; leftmost argument ends up on top of stack

call main

pop ;; caller cleans up
pop
pop

In compilers where this type of calling convention is the case, nothing special need to be done to support the two kinds of main, or even additional kinds. main can be a function of no arguments, in which case it is oblivious to the items that were pushed onto the stack. If it's a function of two arguments, then it finds argc and argv as the two topmost stack items. If it's a platform-specific three-argument variant with an environment pointer (a common extension), that will work too: it will find that third argument as the third element from the top of the stack.

And so a fixed call works for all cases, allowing a single, fixed start-up module to be linked to the program. That module could be written in C, as a function resembling this:

/* I'm adding envp to show that even a popular platform-specific variant
can be handled. */
extern int main(int argc, char **argv, char **envp);

void __start(void)
{
/* This is the real startup function for the executable.
It performs a bunch of library initialization. */

/* ... */

/* And then: */
exit(main(argc_from_somewhere, argv_from_somewhere, envp_from_somewhere));
}

In other words, this start module just calls a three-argument main, always. If main takes no arguments, or only int, char **, it happens to work fine, as well as if it takes no arguments, due to the calling conventions.

If you were to do this kind of thing in your program, it would be nonportable and considered undefined behavior by ISO C: declaring and calling a function in one manner, and defining it in another. But a compiler's startup trick does not have to be portable; it is not guided by the rules for portable programs.

But suppose that the calling conventions are such that it cannot work this way. In that case, the compiler has to treat main specially. When it notices that it's compiling the main function, it can generate code which is compatible with, say, a three argument call.

That is to say, you write this:

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

But when the compiler sees it, it essentially performs a code transformation so that the function which it compiles looks more like this:

int main(int __argc_ignore, char **__argv_ignore, char **__envp_ignore)
{
/* ... */
}

except that the names __argc_ignore don't literally exist. No such names are introduced into your scope, and there won't be any warning about unused arguments.
The code transformation causes the compiler to emit code with the correct linkage which knows that it has to clean up three arguments.

Another implementation strategy is for the compiler or perhaps linker to custom-generate the __start function (or whatever it is called), or at least select one from several pre-compiled alternatives. Information could be stored in the object file about which of the supported forms of main is being used. The linker can look at this info, and select the correct version of the start-up module which contains a call to main which is compatible with the program's definition. C implementations usually have only a small number of supported forms of main so this approach is feasible.

Compilers for the C99 language always have to treat main specially, to some extent, to support the hack that if the function terminates without a return statement, the behavior is as if return 0 were executed. This, again, can be treated by a code transformation. The compiler notices that a function called main is being compiled. Then it checks whether the end of the body is potentially reachable. If so, it inserts a return 0;



Related Topics



Leave a reply



Submit