File descriptors
From NetBSD Wiki
Introduction
In Unix, the kernel builds a table in the process table for every file a process (running program) has open. This table generally is implemented as a simple array, with the entries pointing to inodes from the filesystem layer. The number of the entry in the table is called the file descriptor, and is implemented as an integer which can be passed to the various filesystem operations. For example, open(2) returns this number, which is then accepted by read(2) and write(2).
Standard descriptors
The shell always allocates the filedescriptors 0, 1 and 2 for every process. These are known as stdin, stdout and stderr, also known as standard input, standard output and standard error. Because these numbers are "well-known" (the same for every shell and every application), they can be easily redirected to "real" files or even connected to other processes' standard file descriptors for direct communication between the two.
POSIX defines the preprocessor variables STDIN_FILENO, STDOUT_FILENO and STDERR_FILENO in unistd.h. There are ANSI file handle equivalents for these called stdin, stdout and stderr in stdio.h.
Descriptors and ANSI C file pointers
There is often some confusion about what to use in C programs; the ANSI C FILE * pointers from the fopen(3) family, or the POSIX file descriptors from open(2) and friends. The answer is not very easy.
The difference is that in ANSI C, file handles are buffered by the C library (by default, but you can change this with setbuf(3)) while POSIX file descriptors are unbuffered by the library (there may still be a line buffer in effect by the OS, for example).
The open(2) family of functions is a direct interface to the open system call, where fopen is implemented by the C library with a deal of buffering and translation to file descriptors in between. This has advantages as well as disadvantages. The advantages are, among others, that you can use hacks like ungetc(3) on buffers to push a character back into the buffer as if it hadn't been read yet and that ANSI C functions are (slightly) more portable than POSIX functions. A disadvantage is that you never know when the bytes are written, which would be a severe disadvantage if you're interfacing with a device driver (a file device from /dev), for example.
The rule of thumb would be: Always use ANSI C functions, except when interfacing directly with the kernel via device drivers.
Of course, you can always switch from ANSI file handles to raw descriptors using the fileno(3) function and back from descriptors to ANSI file handles with fdopen(3).
