My understanding is that it was related to the behaviour of system calls.
Consider the open()
system call; if it is successful, it returns a non-negative integer, which is the file descriptor that was created. However, down at the assembler level (where there's a special, non-C instruction that traps into the kernel), when an error is returned, it is returned as a negative value. When it detects an error return, the C code wrapper around the system call stores the negated value into errno
(so errno
has a positive value), and the function returns -1.
For some other system calls, the negative return code at the assembler level is still negated and placed into errno
and -1 is returned. However, these system calls have no special value to return, so zero was chosen to indicate success. Clearly, there is a large variety of system calls, but most manage to fit these conventions. For example, stat()
and its relatives return a structure, but a pointer to that structure is passed as an input parameter, and the return value is a 0 or -1 status. Even signal()
manages it; -1 was SIG_DFL and 0 was SIG_IGN, and other values were function pointers. There are a few system calls with no error return - getpid()
, getuid()
and so on.
This zero-indicates-success mechanism was then emulated by other functions which were not actually system calls.