← Return

The anatomy of the C++ main function

| 4 minutes read |  #code  #cpp

In C++, main is a global function invoked by the implementation at the program startup. It acts as the program's entry point, and most of the code you write executes in it.

Let's look at how to declare the main function, what happens before and after it executes, and the differences between various implementations.

Function declaration

Every C++ program must contain a global function main. An implementation cannot predefine it, which means you must define it explicitly.

The C++ standard requires implementations to support at least two variants of main function declarations:

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

You can sometimes find the second variant written in a slightly different way:

int main(int argc, char** argv)

The declared return type of the main function must be int. Some compilers are not so strict about this. For example, Visual Studio C++ permits the return type to be void, but emits a warning if you do so.

Program execution

Before and after the execution of the main function, a few things must happen.

First, the implementation initializes all non-local objects with static storage duration (global variables). Next, it starts the main thread of execution and invokes the main function. After the execution of the main function finishes, all objects with automatic storage duration (local variables) are destroyed, just like with a regular function. Finally, the std::exit is invoked with the return value of the main function. Calling std::exit (manually or automatically) destroys all non-local objects with static storage duration (global variables) and terminates the program.

The operating system stores the program's exit code and allows the user to retrieve it. For example, on Linux, you can get the exit code of the recently executed program from the terminal:

echo $?

You can explicitly return a value in the function definition or omit it. If you don't explicitly return a value, it will default to 0. The following implementations of the main function are equivalent:

int main() {}
int main() { return 0; }

A non-zero exit code is usually an indicator of a program failure.

Command line arguments

The standard way of obtaining command line arguments in a C++ program, is through the parameters of the main function. The following is a declaration of the main function that accepts command line arguments:

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

The argc parameter is the number of arguments passed to the program. If argc is greater than 0, the arguments are supplied in argv[0] through argv[argc-1]. Note that argc cannot have a negative value.

The argv parameter is an array of null-terminated strings containing the arguments passed to the program. The argv[0] usually contains the program's name. In some environments, it can be empty or even null.

The strings in argv have environment specific encoding. On Linux and OSX, it's usually UTF-8. On Windows, it's plain ANSI. However, Windows also supports an alternative way to define the main function to get command line arguments in Unicode:

int wmain(int argc, wchar_t* argv[]) {
  // ...
}

The wchar_t is a C++ standard type for representing wide characters. On Windows, it's 2 bytes long and contains UTF-16 encoded characters. Another way to define the main function on Windows is to use the _tmain macro, which expands to either main or wmain depending on the presence of _UNICODE definition.

The WinMain function is used in windowed applications and comes with its own set of parameters:

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow) {
  // ...
}

You can read about the details of the WinMain function on the MSDN pages.

Environment variables

On Windows, the main and wmain functions offer a non-standard way of obtaining environment variables for a program. Their declarations look like follows:

int main(int argc, char* argv[], char* envp[])
int wmain(int argc, wchar_t* argv[], wchar_t* envp[])

The envp[] parameter is an array of strings containing the variables set in the user's environment. A null element indicates the end of the array. There's a bunch more of implementation specific nuances on Windows. You can read more about them on MSDN.

Exception handling

C++ has a feature called function-try-block. It's a way to associate a sequence of catch clauses with the entire function body. Here's an example:

void foo() try {
}
catch (...) {
}

You can also declare the main function with a function-try-block:

int main() try {
}
catch (...) {
}

The function-try-block of the main function doesn't catch exceptions thrown from the constructors and destructors of non-local objects with static storage duration. They are initialized before the main function executes, so it's impossible for the function-try-block to catch any exceptions thrown from them.

One way to handle exceptions throw by global objects is to set your own terminate handler using std::set_terminate. However, due to all the nuisances involved, I think it's a topic for another time 🙂