A value returned from main() is handled as if it were passed to exit().
When exit(status) is called, "If the value of status 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." (7.22.4.4p5)
Note in particular that EXIT_SUCCESS is neither required to expand to an
expression with a value of 0, nor is it prohibited from doing so. The
same is true of EXIT_FAILURE and 1. EXIT_FAILURE does have to expand
into an expression with a value different from both 0 the value of the
expansion of EXIT_SUCCESS, otherwise it couldn't produce a different
termination status. What happens if you return 1 is covered only by the
last sentence of that paragraph.
You can reasonably expect an implementation to choose the form for each
of those termination statuses in a way that is appropriate for the
target platform (and you should complain if it isn't). It's neither
necessary nor sufficient for the program that checks the termination
status of a C program to know anything about C. It's both necessary and
sufficient that it know how to use the language-specific interfaces to
the platform-specific ways of checking the termination status.
For instance, on unix-like systems, you can use wait(), waitpid(), or
waitid() to wait for the status of a process to change, and to check
what the new status is. The termination status is reported in an int,
but that int does NOT necessarly have the same value that the C program
passed to the exit() routine. If WIFEXITED(termination_status) is true,
then WEXITSTATUS(termination_status) gives you the 8 low-order bits of
the value that was passed to exit(). The rest of that value is
unavailable. BSD has defined a standard meaning for exit statuses of 0
and any number from 64 to EX__MAX, which is currently 78 on my machine -
see sysexits.h for details. Other values can have meanings defined by
the user. If your code takes advantage of that fact, any code that
checks the termination status will need to know what meaning your code
attaches to each of those values.
On other platforms, with little or nothing in the way of an operating
system or a display, a successful exit might be indicated by causing a
light to turn green, while unsuccessful status may be indicated by
causing a light to turn red.