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.