C 2018 5.1.2.2.3 1 tells us what happens in a hosted environment:
If the return type of the main
function is a type compatible with int
, a return from the initial call to the main
function is equivalent to calling the exit
function with the value returned by the main
function as its argument; reaching the }
that terminates the main
function returns a value of 0. If the return type is not compatible with int
, the termination status returned to the host environment is unspecified.
So, in a hosted environment, what you likely think of as the “normal” C environment, return x;
from the initial call to main
is equivalent to exit(x);
, if it was declared with a return type compatible with int
. (C implementations may define other allowed declarations.)
In a freestanding environment, 5.1.2.1 2 tells us:
The effect of program termination in a freestanding environment is implementation-defined.
The behavior of exit
is specified in 7.22.4.4:
3 First, all functions registered by the atexit
function are called, in the reverse order of their registration, except that a function is called after any previously registered functions that had already been called at the time it was registered…
4 Next, all open streams with unwritten buffered data are flushed, all open streams are closed, and all files created by the tmpfile
function are removed.
5 Finally, control is returned to the host environment. If the value of status
[the parameter of exit
] is zero or EXIT_SUCCESS
, an implementation-defined form of the status successful termination is returned. If the value of status
is EXIT_FAILURE
, an implementation-defined form of the status unsuccessful termination is returned. Otherwise the status returned is implementation-defined.