Why did the 16-bit _lopen and _lcreat function return -1 on failure instead of 0?
Some time ago, I discussed why HANDLE
return values are so inconsistent, and I traced it all the way back to the 16-bit _lopen
and _lcreat
functions, which returned -1
on failure.
But why do those functions return -1
on failure instead of zero?
The _lopen
and _lcreat
functions were Windows versions of the C runtime _open
and _creat
functions. The C runtime functions came in four different versions depending on which MS-DOS memory model you were using, and the convention was that when Windows adopted a C runtime function, it used the “large” version with the L
prefix, since that is the most general version.
Okay, so why did _open
and _creat
return -1
on failure?
Because they were MS-DOS-compatible versions of the Unix functions open
and creat
. They even preserve the dropped silent “e” at the end of creat
.
Okay, so why do those functions return -1
on failure?
On Unix, the return value is an integer that represents a file descriptor, valid file descriptors are integers starting with zero. Every process comes with three predefined file descriptors:
Descriptor | Meaning |
---|---|
0 | stdin (standard input) |
1 | stdout (standard output) |
2 | stderr (standard error) |
Files opened by the program begin with file descriptor 3.
The value -1
is used to represent failure because 0 was already taken.
And that value of -1
carried forward, through a chain of backward compatibility, to Win32 as the numeric value of INVALID_
. We saw a little while ago one of the consequences.
3 comments
Or at least, every Unix process assumes that it comes with three predefined file descriptors. There’s nothing to stop you writing something like
foo <&-
to launch a process without a STDIN handle, but the process might not appreciate it.xcopy.exe is not happy if you don’t give it a stdin.
It’s easy to break the assumptions of Unix programmers. There are programs that change their behavior depending on the value of
argv[0]
, usually because you’re supposed to make one or more symlinks with different names. I have to wonder what those programs do if you pass an empty argument list (POSIX says the application “should” ensure that the binary name is given asargv[0]
, but in their base definitions, “should” indicates a portability recommendation rather than a strict requirement, so this is at least somewhat POSIX-legal). In that case, ISO C (and by extension POSIX) require thatargv[0] == NULL
, but POSIX also warns that some applications may dereference it without checking for NULL.