You are asking about issues that are profoundly implementation-dependent. I'll provide a generic description, but you need to realize that your system may accomplish similar goals in a wildly different fashion.
Consider this simple C program:
- world.c:
-
#include <stdio.c>
-
int main(void)
-
{
-
printf("Hello World\n");
-
return 0;
-
}
You may think that you've been invoking the compiler all this time to compile programs like this, but you've actually been invoking the
compiler driver. The compiler driver invokes the following programs one after the other for you.
- The preprocessor replaces single #include lines with the entire contents of the indicated file, omits conditionally uncompiled code, expands macros, and expands any other preprocessor directives.
- The compiler translates a preprocessed source file into assembly language.
- The assembler translates assembly language into an object module.
- The linker combines one or more object modules with referenced archived object modules from libraries (such as the code for printf) and also with the C run-time library (more on that later) to create an executable image.
Now suppose you type the name of your executable image file on the command line. The command line interpreter (or shell) is itself a program that accepts input from the user and parses it into a command word plus argument list. It searches a specific list of directories for a file with the same name as the command word. If it finds a matching file then it magically determines if that file is an executable image, a batch file, or any of the other types of files that it knows how to handle. For an executable image, the shell invokes that executable image, passing it the argument list.
I mentioned earlier that the linker combines your program with the C runtime library. One element of the C runtime element is the startup function (variously called
c0,
crt,
crt0). The startup function is the entry point into the executable image. It sets all uninitialized variables with file scope to zero; it sets all initialized variables to their initial values; it packs up the argument list into
argc and
argv; and it calls your
main function. When
main returns, the startup function does whatever cleanup is warranted and exits back to the shell.
By the way, notice that the startup function calls your
main function without referencing any prototype you may or may not have provided for
main -- it takes for granted that your
main honors the C Standard and returns an int, not
void. There is no point providing a function prototype for
main, it won't be used.