HOWTO · C
File Descriptor in C
This article explains about the file descriptor in C Programming Language.
On this page
This article will discuss the file descriptor in the C programming language.
How File Descriptors Work
A file descriptor is a small nonnegative integer that a process uses as a handle for an open I/O resource. The integer is an entry in that process’s descriptor table; it is not the file itself and does not identify a resource globally. The operating system associates the descriptor with open-file state, including the current file offset and access mode. Several descriptors can refer to the same open-file state, for example after duplication or inheritance across process creation.
A successful open() returns the lowest available descriptor in the calling process. A value such as 3 is common in examples when the standard descriptors are already in use, but programs must use the value returned by the call rather than assume a number. The descriptor remains usable until it is closed or otherwise replaced. (read())
Standard File Descriptors in C
Unix-like environments conventionally start a process with three standard descriptors:
| Descriptor | Usual name | Typical purpose |
|---|---|---|
0 |
standard input (stdin) |
Read input |
1 |
standard output (stdout) |
Write regular program output |
2 |
standard error (stderr) |
Write diagnostics |
These are conventions, not guarantees about a particular device. A shell can redirect standard input or output to a file or pipe, and a program can receive other resources at these numbers. In C, the stdin, stdout, and stderr stream objects are provided by <stdio.h>; their usual underlying descriptors are 0, 1, and 2 in POSIX environments. Use the stream functions for FILE * streams and descriptor calls when working directly with an integer descriptor. Do not mix buffered stream operations and direct descriptor operations on the same underlying open file without coordinating them, because their buffering and offsets can diverge.
Open, Read, and Close a File in C
The POSIX interfaces are declared by <fcntl.h> and <unistd.h>. open() takes a path and flags, then returns a descriptor or -1 on failure and sets errno. Choose the access mode with O_RDONLY, O_WRONLY, or O_RDWR. Add flags such as O_CREAT only when needed. Whenever O_CREAT is present, pass a third mode argument, such as 0600; the process umask can remove permission bits from the requested mode. O_CREAT creates a missing file, but does not truncate an existing one. Use O_TRUNC only when intentionally replacing existing contents.
read(fd, buffer, count) stores up to count bytes and returns the number actually read. For a regular file and a positive count, a return of 0 indicates that the current file offset is at end of file. For other descriptor types, a zero-byte result does not universally prove EOF; for example, terminal settings can allow it without EOF, so interpret the result according to that descriptor type’s behavior. If count is 0, read() may also return 0 without indicating EOF. A positive value can be smaller than the requested count, so process only that many bytes and continue if more input is required. A return of -1 indicates failure. For interruptible calls, retry when errno is EINTR; handle other errors according to the application. The buffer contains bytes, not necessarily a C string, so do not print it with %s unless you have added a terminator within the allocated space.
close(fd) releases the descriptor and returns 0 on success or -1 on failure. Closing does not mean that every write is durably stored; code that needs persistence has additional synchronization requirements. Check the close result when it matters. After a failed close(), do not blindly retry the same descriptor: its state can be platform-dependent, and that number may already have been reused.
Run this self-contained POSIX example. It creates a temporary file, writes a short payload, reopens it for reading, copies the bytes to standard output, and removes the temporary file. Each failed system call writes a diagnostic to standard error, and main() returns a failure status if any part of the transfer or cleanup fails. A successful run writes only the payload to standard output.
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
static int write_all(int fd, const char *data, size_t length) {
size_t done = 0;
while (done < length) {
ssize_t n = write(fd, data + done, length - done);
if (n < 0 && errno == EINTR) continue;
if (n == 0) errno = EIO;
if (n <= 0) return -1;
done += (size_t)n;
}
return 0;
}
int main(void) {
char path[] = "/tmp/file-descriptor-example-XXXXXX";
const char payload[] = "File descriptor example\n";
char buffer[16];
int created = mkstemp(path);
if (created < 0) {
perror("mkstemp");
return EXIT_FAILURE;
}
int failed = 0;
if (write_all(created, payload, sizeof(payload) - 1) < 0) {
perror("write temporary file");
failed = 1;
}
if (close(created) < 0) {
perror("close temporary file");
failed = 1;
}
if (failed) {
if (unlink(path) < 0) perror("unlink temporary file");
return EXIT_FAILURE;
}
int fd = open(path, O_RDONLY);
if (fd < 0) {
perror("open temporary file");
if (unlink(path) < 0) perror("unlink temporary file");
return EXIT_FAILURE;
}
failed = 0;
for (;;) {
ssize_t n = read(fd, buffer, sizeof(buffer));
if (n < 0 && errno == EINTR) continue;
if (n < 0) {
perror("read temporary file");
failed = 1;
break;
}
if (n == 0) break;
if (write_all(STDOUT_FILENO, buffer, (size_t)n) < 0) {
perror("write standard output");
failed = 1;
break;
}
}
if (close(fd) < 0) {
perror("close input file");
failed = 1;
}
if (unlink(path) < 0) {
perror("unlink temporary file");
failed = 1;
}
return failed ? EXIT_FAILURE : EXIT_SUCCESS;
}
File descriptor example
The read loop handles short reads by writing each returned byte range and then reading again until end of file. It retries interrupted reads and writes, and it closes the descriptor on the normal path and after read or output failures. The temporary file is created with mkstemp() for this demonstration; in an application, replace that setup with the path and creation policy appropriate to the task. This example uses POSIX interfaces and is not ISO C-only.