HOWTO · C
Descriptor de archivo en C
Este artículo explica sobre el descriptor de archivo en el lenguaje de programación C.
En esta página
Este artículo explica los descriptores de archivo en el lenguaje de programación C.
Cómo funcionan los descriptores de archivo
Un descriptor de archivo es un entero pequeño y no negativo que un proceso usa como identificador de un recurso de E/S abierto. El entero es una entrada de la tabla de descriptores de ese proceso: no es el archivo ni identifica un recurso de forma global. El sistema operativo asocia el descriptor con el estado del archivo abierto, incluido el desplazamiento actual y el modo de acceso. Varios descriptores pueden referirse al mismo estado de archivo abierto, por ejemplo, después de duplicar un descriptor o heredarlo al crear un proceso.
Una llamada exitosa a open() devuelve el descriptor disponible más bajo del proceso que la realiza. En los ejemplos es común ver un valor como 3 cuando los descriptores estándar ya están en uso, pero los programas deben usar el valor devuelto por la llamada, sin presuponer un número. El descriptor sigue disponible hasta que se cierra o se reemplaza de otra manera. (read())
Descriptores de archivo estándar en C
En los entornos tipo Unix, normalmente se inicia cada proceso con tres descriptores estándar:
| Descriptor | Nombre habitual | Uso típico |
|---|---|---|
0 |
entrada estándar (stdin) |
Leer datos de entrada |
1 |
salida estándar (stdout) |
Escribir la salida normal del programa |
2 |
error estándar (stderr) |
Escribir mensajes de diagnóstico |
Son convenciones, no garantías sobre un dispositivo concreto. Un shell puede redirigir la entrada o la salida estándar a un archivo o una tubería, y un programa puede recibir otros recursos en esos números. En C, los objetos de flujo stdin, stdout y stderr se proporcionan mediante <stdio.h>; en entornos POSIX, sus descriptores subyacentes habituales son 0, 1 y 2. Usa las funciones de flujo para trabajar con flujos FILE * y las llamadas de descriptor cuando trabajes directamente con un descriptor entero. No mezcles operaciones con flujos almacenados en búfer y operaciones directas con descriptores sobre el mismo archivo abierto sin coordinarlas, ya que los búferes y los desplazamientos pueden dejar de coincidir.
Abrir, leer y cerrar un archivo en C
Las interfaces POSIX se declaran en <fcntl.h> y <unistd.h>. open() recibe una ruta y unas opciones; devuelve un descriptor o -1 si falla, y establece errno. Elige el modo de acceso con O_RDONLY, O_WRONLY u O_RDWR. Añade opciones como O_CREAT solo cuando sean necesarias. Si incluyes O_CREAT, pasa un tercer argumento mode, por ejemplo, 0600; la máscara de creación de archivos del proceso puede quitar bits de permiso del modo solicitado. O_CREAT crea el archivo si no existe, pero no trunca uno existente. Usa O_TRUNC solo cuando quieras reemplazar intencionadamente el contenido existente.
read(fd, buffer, count) almacena hasta count bytes y devuelve la cantidad que realmente leyó. En un archivo regular, si count es mayor que 0, un resultado de 0 indica que el desplazamiento actual del archivo está al final. En otros tipos de descriptor, un resultado de cero bytes no demuestra siempre que se haya llegado al fin de archivo; por ejemplo, ciertos ajustes de terminal pueden producirlo sin que haya fin de archivo, así que interpreta el resultado según el comportamiento de ese tipo de descriptor. Si count es 0, read() también puede devolver 0 sin que eso indique el fin de archivo. El valor devuelto puede ser positivo y menor que la cantidad solicitada: procesa solo esos bytes y vuelve a leer si necesitas más datos. Un resultado de -1 indica un error. Si la llamada puede interrumpirse, vuelve a intentarlo cuando errno sea EINTR; gestiona los demás errores según lo requiera la aplicación. El búfer contiene bytes, que no tienen por qué formar una cadena de C; no lo imprimas con %s salvo que hayas añadido un terminador dentro del espacio asignado.
close(fd) libera el descriptor y devuelve 0 si tiene éxito o -1 si falla. Cerrar un descriptor no garantiza que todas las escrituras hayan quedado almacenadas de forma duradera; el código que necesita persistencia debe cumplir requisitos adicionales de sincronización. Comprueba el resultado de close() cuando sea importante. Si close() falla, no vuelvas a intentarlo a ciegas con el mismo descriptor: su estado puede depender de la plataforma y ese número quizá ya se haya reutilizado.
Ejecuta este ejemplo POSIX autocontenido. Crea un archivo temporal, escribe en él una carga breve, lo vuelve a abrir para leer, copia los bytes a la salida estándar y elimina el archivo temporal. Cada llamada al sistema que falla escribe un diagnóstico en el error estándar, y main() devuelve un estado de fallo si falla cualquier parte de la transferencia o de la limpieza. Si la ejecución tiene éxito, escribe únicamente la carga en la salida estándar. El código y las etiquetas o el texto visible que contiene se conservan exactamente como en el ejemplo y permanecen en inglés.
#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
El bucle de lectura gestiona las lecturas parciales: escribe cada intervalo de bytes devuelto y vuelve a leer hasta llegar al fin de archivo. Reintenta las lecturas y escrituras interrumpidas, y cierra el descriptor tanto en la ruta normal como después de errores de lectura o de salida. Para esta demostración, el archivo temporal se crea con mkstemp(); en una aplicación, sustituye esa preparación por la ruta y la política de creación que requiera la tarea. Este ejemplo usa interfaces POSIX y no se limita a ISO C.