The termination status of a process is 16-bit wide:
The above exit code and termination signal number are exclusive as a process ends either by an exit or by the receipt of an uncaught signal.
In C/C++ applications, the status of a terminated process can be obtained with services like wait() or waitpid(). And the status content can be analyzed with dedicated macros described in the latter manpage: WIFEXITED(), WIFSIGNALED(), ...
According to the preceding, the exit code being 8-bit wide, must have the values: 0 ≤ exit code ≤ 255. Moreover, the exit code 0 is used to report a successful execution and any other value is used to denote an error. Negative values are forbidden or at least would result in misleading status as only the 8 bits of the LSB byte would be used to fill the exit code part of the status. For example, a program calling exit with -255 would result with an exit code equal to 1 as -255 is coded 0xFFFFFF01 (in a 32-bit system).
There are some other considerations to take in account.
In the Bash shell source code, the internal shell.h header defines the following list of reserved exit codes for the interpreter itself or its built-ins:
/* Values that can be returned by execute_command (). */
#define EXECUTION_FAILURE 1c
#define EXECUTION_SUCCESS 0
/* Usage messages by builtins result in a return status of 2. */
#define EX_BADUSAGE 2
#define EX_MISCERROR 2
/* Special exit statuses used by the shell, internally and externally. */
#define EX_RETRYFAIL 124
#define EX_WEXPCOMSUB 125
#define EX_BINARY_FILE 126
#define EX_NOEXEC 126
#define EX_NOINPUT 126
#define EX_NOTFOUND 127
So, in projects mixing C or C++ code and shell scripts, it may be useful to differentiate exit codes from shell and from compiled code. Hence, the range of values [124,127] should not be used as exit codes in C or C++ applications.
In the shell, the status of a command (built-in or any program) is obtained with $? . The latter is set as follow
So, at status analysis time, if we want to discriminate exited commands from "interrupted by signal" commands, an exit code must not be bigger than 128.
As a summary, to have programs cohabit properly with shell, the exit code range should be:
0 ≤ exit code ≤ 123. The value 0 denotes a successful execution. Any other value denotes an error.
The values 124 to 127 are used by the shell itself.
The values bigger than 128 are used by the shell internals to point out a signal receipt (the received signal number being: $? - 128).
man 2 waitand you'll see that termination by a signal is a separate case that doesn't offer aWEXITSTATUSvalue. This generally gets lost in translation because shells like Bash conflate different types of termination into a single "exit status".