C++ / GETTING STARTED
Your first program and what main returns
Write, build, and run a minimal C++ program, and use main's int return value to report success or failure to the shell that started it.
What you will learn
- Write a complete minimal program: one int main() definition and nothing else
- Read a program's exit status after running it with echo $? on Linux or macOS
- Signal failure with return EXIT_FAILURE and send the message to std::cerr
- Explain why main may fall off the end but other int-returning functions may not
Understanding Your first program and what main returns
Nothing in a C++ program runs because it sits at the top of the file. After startup code prepares the runtime and initializes objects with static storage duration, the implementation calls exactly one function by name: main. That is the mental model worth keeping — main is an ordinary function that you define and somebody else calls, and like any function it hands a value back to its caller. Here the caller is the host environment: your shell, an IDE's run button, a test runner, or a CI job.
The int that main returns is an exit status, not a result. It answers one question for the process that started you: did this run succeed? Zero means success and every nonzero value means failure, which is how make, the shell's && operator, and CI pipelines react to your program without reading a word of its output. Anything a human should see goes to std::cout or std::cerr; the status is a single number meant for other programs.
main also has rules no other function has. It must return int, so void main is not C++, and it is the only function where reaching the closing brace is defined to behave as return 0 — that is why hello-world programs with no return statement still report success. Returning from main destroys main's local objects and then does what std::exit does: destroys static objects and flushes the standard streams, so returning is an orderly shutdown rather than an abrupt stop.
Because the status is the only channel other tools read, choose its value deliberately: return 0 on the success path and a nonzero value on each failure path, close to where the failure is detected.
<iostream>
int main()
{
std::cout << "Program started\n";
int status = 0;
std::cout << "Returning " << status << " to the operating system\n";
return status;
}
main is a function the runtime calls for you, and the int it returns is the program's exit status: zero for success, any nonzero value for failure.
Worked examples
Falling off the end of main
Shows that omitting the return statement in main still produces an exit status of 0.
<iostream>
int main()
{
std::cout << "no return statement in this main\n";
}
Example explained
Line 1Control reaches the closing brace of main, which the standard defines as equivalent to return 0, so echo $? prints 0.
Line 2g++ -Wall -Wextra stays silent here, while the same omission in any other int-returning function warns under -Wreturn-type and is undefined behaviour if that path is taken.
Line 3Adding return 0 explicitly changes nothing in the generated program; it only documents the intent.
Reporting failure with EXIT_FAILURE
Uses main's return value to tell the caller that the run failed, with the message on std::cerr.
<cstdlib>
<iostream>
int checksum()
{
return 7;
}
int main()
{
const int expected = 8;
const int actual = checksum();
if (actual != expected) {
std::cerr << "checksum mismatch: expected " << expected
<< ", got " << actual << '\n';
return EXIT_FAILURE;
}
std::cout << "checksum ok\n";
return EXIT_SUCCESS;
}
Example explained
Line 1checksum() gives 7 and expected is 8, so the failure branch runs and the success line is never reached.
Line 2The diagnostic goes to std::cerr, so it is still visible when someone redirects stdout into a file.
Line 3EXIT_SUCCESS and EXIT_FAILURE come from <cstdlib>; EXIT_SUCCESS is 0, and EXIT_FAILURE is an implementation-chosen nonzero value, typically 1.
Line 4Run this as ./prog || echo failed and the shell prints failed, because it inspects the status, not the text.
The status is not a full int on POSIX
Demonstrates that a shell keeps only the low eight bits of whatever main returns.
<iostream>
int main()
{
const int status = 300;
std::cout << "main returns " << status << '\n';
std::cout << "a POSIX shell will report " << (status & 0xFF) << '\n';
return status;
}
Example explained
Line 1main's return type is int, so returning 300 is legal C++ and compiles without complaint.
Line 2The process exit status on POSIX carries only the low 8 bits, so echo $? shows 300 & 0xFF, which is 44.
Line 3The same rule turns return -1 into 255, which is why -1 is a poor way to spell failure.
Line 4Keeping statuses between 0 and 125 avoids the values shells reserve for signals and startup errors.
Important notes
Only two signatures are portable: int main() and int main(int argc, char* argv[]). main also may not be overloaded, called from your own code, have its address taken, or be declared static, inline, or constexpr.
How the int reaches the outside world is implementation-defined: POSIX shells expose only the low 8 bits in $?, while Windows keeps the full value in ERRORLEVEL and GetExitCodeProcess.
Common mistakes
Writing void main(): g++ rejects it with "error: '::main' must return 'int'", so the program never builds, even though older tutorials and some Windows compilers tolerate it.
Treating the return value as output: return 42 prints nothing at all, and the program looks broken until you add a std::cout statement.
Using return 1 or return -1 to mean "finished fine": scripts, Makefiles and CI steps read nonzero as failure and stop, and -1 additionally shows up as 255.
Try it yourself
Change, predict, then run
In a browser editor, write a program that prints your name on one line and ends with return 7, run it, and note that the printed text is unaffected while the reported exit code is 7. Then delete the return statement, predict the exit code before running, and check your prediction.
Open the C++ workspaceCheck your understanding
A program prints done to std::cout and its main ends with return 3. A build script runs it as ./prog && echo ok. What happens, and why?
- done appears and ok does not, because && continues only when the exit status is 0 and 3 means failure
- done and ok both appear, because the program finished without crashing so the shell counts it as successful
- Only ok appears, because return 3 replaces the program's output with its status
- Nothing appears, because the compiler rejects a main that returns a value other than 0
Show answer
The shell decides success from the status main returned, not from whether the program ran to completion or printed anything, so a status of 3 stops the && chain. Option 1 is tempting because the program really did its job, but 0 is the only way to say so; any nonzero value is a failure report.