Chris Cooper
Siempre me ha parecido extraño que la función C fopen()
toma una const char *
como segundo argumento. Creo que sería más fácil leer su código e implementar la biblioteca si hubiera máscaras de bits definidas en stdio.h
como IO_READ
y tal, por lo que podría hacer cosas como:
FILE *myFile = fopen("file.txt", IO_READ | IO_WRITE);
¿Hay una razón programática para la forma en que realmente es, o es simplemente histórico? (es decir ‘Esa es la forma como es.’)
jonathan leffler
Creo que una de las ventajas de la cadena de caracteres en lugar de una simple máscara de bits es que permite extensiones específicas de la plataforma que no son configuraciones de bits. Puramente hipotéticamente:
FILE *fp = fopen("/dev/something-weird", "r+,bs=4096");
Para este artilugio, el open()
a la llamada se le debe indicar el tamaño del bloque, y diferentes llamadas pueden usar tamaños radicalmente diferentes, etc. Por supuesto, la E/S se ha organizado bastante bien ahora (tal no era el caso originalmente: los dispositivos eran enormemente diversos y los mecanismos de acceso estaban lejos de estar unificados). ), por lo que rara vez parece ser necesario. Pero el argumento del modo abierto con valor de cadena permite esa extensibilidad mucho mejor.
En el mainframe MVS o/s de IBM, el fopen()
De hecho, la función toma argumentos adicionales en las líneas generales descritas aquí, como lo señaló Andrew Henle (¡gracias!). La página del manual incluye la llamada de ejemplo (ligeramente reformateada):
FILE *fp = fopen("myfile2.dat", "rb+, lrecl=80, blksize=240, recfm=fb, type=record");
el subyacente open()
tiene que ser aumentada por la ioctl()
(control de E/S) llamada o fcntl()
(control de archivos) o funciones ocultándolos para lograr efectos similares.
-
gracias jonathan Esa es otra ventaja interesante de las cuerdas que no conocía.
– Chris Cooper
27 de marzo de 2010 a las 21:07
-
Ver MVS de IBM
fopen()
documentación para ejemplos reales de esto.–Andrew Henle
10 de julio de 2018 a las 12:03
-
en Windows puede recibir la codificación:
fopen("newfile.txt", "rt+, ccs=encoding")
– phuclv
19 de febrero de 2020 a las 7:57
-
En cambio
fopen
podría haber sido una función variable que toma indicadores binarios y argumentos adicionales definidos por la implementación opcional.– usuario16217248
30/09/2022 a las 18:44
-
@usuario16217248 En cambio
fopen
podría haber sido una función variádica… No con la API actual. A diferencia deopen()
dóndeO_CREAT
que se establece es la bandera utilizada para indicar la presencia delmode
argumento, no hay forma de usar las cadenas pasadas afopen()
para indicar la ausencia o presencia de un argumento extendido de cualquier tipo sin extender el contenido de esta cadena más allá de los valores estándar existentes, y cambiaría el prototipo de la función para todos, con consecuencias potencialmente desconocidas para el código ya compilado. Y si vas a hacer todo eso…–Andrew Henle
28 de marzo a las 18:57
Dennis Ritchie (en 1993) escribió un artículo sobre la historia de Cy cómo evolucionó gradualmente desde B. Algunas de las decisiones de diseño estuvieron motivadas por evitar cambios en la fuente del código existente escrito en B o versiones embrionarias de C.
En particular, Lesk escribió un ‘paquete de E/S portátil’ [Lesk 72] que luego fue reelaborado para convertirse en las rutinas de “E/S estándar” de C
El preprocesador C no se introdujo hasta 1972/3, por lo que el paquete de E/S de Lesk se escribió sin él. (En los primeros años de todavía no C, los punteros encajaban en números enteros en las plataformas que se usaban, y era totalmente normal asignar un valor de retorno implícito a un puntero).
Muchos otros cambios ocurrieron alrededor de 1972-3, pero el más importante fue la introducción del preprocesador, en parte a instancias de Alan Snyder. [Snyder 74]
Sin #include
y #define
una expresión como IO_READ | IO_WRITE
no era una opción.
Las opciones en 1972 para qué fopen
Las llamadas que podrían verse en una fuente típica sin CPP son:
FILE *fp = fopen("file.txt", 1); // magic constant integer literals
FILE *fp = fopen("file.txt", 'r'); // character literals
FILE *fp = fopen("file.txt", "r"); // string literals
Los literales enteros mágicos son obviamente horribles, por lo que desafortunadamente la opción obviamente más eficiente (que Unix adoptó más tarde para open(2)
) fue descartado por falta de un preprocesador.
Obviamente, un carácter literal no es extensible; presumiblemente, eso era obvio para los diseñadores de API incluso en ese entonces. Pero habría sido suficiente (y más eficiente) para implementaciones tempranas de fopen
: Solo admitían cadenas de un solo carácter, buscando *mode
ser r
, w
o a
. (Vea la respuesta de @Keith Thompson). Aparentemente r+
para leer + escribir (sin truncar) vino más tarde. (Ver fopen(3)
para la versión moderna.)
c lo hizo tener un tipo de datos de carácter (agregado a B 1971 como uno de los primeros pasos en la producción de C embrionario, por lo que todavía era nuevo en 1972. El B original no tenía char
habiendo sido escrito para máquinas que empaquetan múltiples caracteres en una palabra, por lo que char()
era una función que indexaba una cadena! Consulte el artículo de historia de Ritchie).
El uso de una cadena de un solo byte es efectivamente pasar un char
por const-reference, con toda la sobrecarga adicional de los accesos a la memoria porque las funciones de la biblioteca no pueden estar en línea. (Y los compiladores primitivos probablemente no estaban insertando nada, ni siquiera funciones triviales (a diferencia de fopen) en la misma unidad de compilación donde reduciría el tamaño total del código para alinearlos; las funciones auxiliares diminutas de estilo moderno dependen de compiladores modernos para alinearlas).
PD: la respuesta de Steve Jessop con la misma cita me inspiró a escribir esto.
Posiblemente relacionado: valor de retorno de strcpy(). strcpy
probablemente también fue escrito bastante temprano.
-
¿Por qué puede
fseek
usar constantes enteras (SEEK_SET
,SEEK_CUR
,SEEK_END
) pero nofopen
?– usuario16217248
17 ene a las 17:23
-
@user16217248: Buena pregunta; Tengo curiosidad por saber si existía en el código original de Lesk que se convirtió en C stdio. Si se agregó más tarde, esa sería la razón obvia. De lo contrario, quizás el código inicial en algún momento tuvo que usar números enteros constantes mágicos antes de que existiera CPP. O posiblemente se usó menos y no fue tan doloroso cambiarlo.
– Peter Cordes
17 ene a las 17:46
-
De todos modos, creo que deberían haber cambiado el
fopen
interfaz tan pronto como las constantes nombradas estuvieron disponibles antes de que fuera demasiado tarde (lo es ahora)– usuario16217248
17 ene a las 18:56
-
@ user16217248: Aparentemente, pusieron un gran énfasis en la compatibilidad con versiones anteriores con las bases de código existentes incluso muy temprano, para el código que se escribió antes de que C fuera estandarizado. Al igual que x86, la compatibilidad con versiones anteriores fue quizás una de las razones del éxito inicial, pero ahora es una carga cuyo diseño no se puede cambiar de manera realista.
– Peter Cordes
17 ene a las 23:38
Tuomas Pelkonen
Una palabra: legado. Desafortunadamente tenemos que vivir con eso.
Solo especulación: tal vez en ese momento un const char *
Parecía una solución más flexible, porque no está limitada de ninguna manera. Una máscara de bits solo puede tener 32 valores diferentes. Parece un YAGNI a mi ahora
Más especulaciones: los tipos eran vagos y escribían "rb"
requiere menos tipeo que MASK_THIS | MASK_THAT
🙂
-
¿Por qué sin embargo? Las máscaras parecen la elección natural aquí, especialmente durante el día.
fopen
fue diseñado.– GManNickG
25 de marzo de 2010 a las 21:01
-
“Una máscara de bits solo podía tener 32 valores diferentes”: cuando se inventó C, una máscara de bits solo podía tener 16 valores diferentes.
– Programador de Windows
26 de marzo de 2010 a las 4:39
-
@Programador de Windows: No, C no tiene un tipo de máscara de bits. Cualquier tipo integral funcionará (preferiblemente sin firmar), y
unsigned long
es un mínimo de 32 bits.–David Thornley
26 de marzo de 2010 a las 14:22
-
Cuando se inventó C, el segundo parámetro para abrir () tenía tipo int, int tenía 16 bits, no existía unsigned y long no existía. El segundo parámetro para abrir() era un int que se usaba como máscara de bits. Dado que el int se usaba como una máscara de bits, 16 bits podrían representar 16 valores diferentes.
– Programador de Windows
29 de marzo de 2010 a las 2:26
-
Y tenga en cuenta que en la época en que se definió y describió la biblioteca de E/S estándar (Versión 7 Unix y anteriores), el
open()
La llamada al sistema solo tomó dos argumentos. Siopen()
falló porque el archivo no existía, tuviste que llamarcreat()
con el nombre y el modo para crear el archivo.–Jonathan Leffler
10 de julio de 2018 a las 18:38
Debo decir que estoy agradecido por ello: sé escribir “r” en lugar de IO_OPEN_FLAG_R o fue IOFLAG_R o SYSFLAGS_OPEN_RMODE o lo que sea
miguel rebabas
Especularía que es uno o más de los siguientes (desafortunadamente, no pude encontrar rápidamente ningún tipo de referencia de apoyo, por lo que probablemente siga siendo una especulación):
- Kernighan o Ritchie (o quien haya ideado la interfaz para
fopen()
) simplemente le gustó la idea de especificar el modo usando una cadena en lugar de un mapa de bits - Es posible que hayan querido que la interfaz fuera similar pero notablemente diferente a la de Unix.
open()
interfaz de llamada al sistema, por lo que sería a la vez familiar pero no se compilaría por error con constantes definidas para Unix en lugar de la biblioteca C
Por ejemplo, digamos que el mítico estándar C fopen()
que tomó un parámetro de modo de mapa de bits usó el identificador OPENMODE_READONLY
para especificar que el archivo de hoy está especificado por la cadena de modo “r”. Ahora, si alguien hiciera la siguiente llamada en un programa compilado en una plataforma Unix (y que el encabezado que define O_RDONLY
se ha incluido):
fopen( "myfile", O_RDONLY);
No habría ningún error del compilador, pero a menos que OPENMODE_READONLY
y O_RDONLY
se definieron para ser el mismo bit que obtendría un comportamiento inesperado. Por supuesto, tendría sentido que los nombres estándar de C se definieran de la misma manera que los nombres de Unix, pero tal vez querían evitar la necesidad de este tipo de acoplamiento.
Por otra parte, esto podría no haber pasado por sus mentes en absoluto…
keith thompson
La primera referencia a fopen
que encontré está en la primera edición de “The C Programming Language” (K&R1) de Kernighan & Ritchie, publicado en 1978.
Muestra un ejemplo de implementación de fopen
, que presumiblemente es una versión simplificada del código en la implementación de la biblioteca estándar C de la época. Aquí hay una versión abreviada del código del libro:
FILE *fopen(name, mode)
register char *name, *mode;
{
/* ... */
if (*mode != 'r' && *mode != 'w' && *mode != 'a') {
fprintf(stderr, "illegal mode %s opening %s\n",
mode, name);
exit(1);
}
/* ... */
}
Mirando el código, el mode
se esperaba que fuera una cadena de 1 carácter (no "rb"
, sin distinción entre texto y binario). Si pasaba una cadena más larga, cualquier carácter que pasara del primero se ignoraba silenciosamente. Si pasaste un inválido mode
, la función imprimiría un mensaje de error y terminaría su programa en lugar de devolver un puntero nulo (supongo que la versión real de la biblioteca no hizo eso). El libro enfatizó el código simple sobre la verificación de errores.
Es difícil estar seguro, especialmente dado que el libro no pasa mucho tiempo explicando el mode
parámetro, pero parece que se definió como una cadena solo por conveniencia. Un solo personaje también habría funcionado, pero una cadena al menos hace posible la expansión futura (algo que el libro no menciona).
steve jesop
Dennis Ritchie tiene esto que decir, desde http://cm.bell-labs.com/cm/cs/who/dmr/chist.html
En particular, Lesk escribió un ‘paquete de E/S portátil’ [Lesk 72] que luego fue reelaborado para convertirse en las rutinas de “E/S estándar” de C
Entonces digo preguntar mike lesk, publique el resultado aquí como respuesta a su propia pregunta y gane montones de puntos por ello. Aunque es posible que desee que la pregunta suene un poco menos como una crítica 😉
-
No existía ningún tipo de datos como const char * cuando Lesk escribió ese paquete. No, espera. De manera ambigua, el tipo de datos podría o no haber existido dependiendo de cómo la implementación manejara las constantes de cadena, pero no había forma de que un programador de C especificara ese tipo de datos en un programa.
– Programador de Windows
25 de marzo de 2010 a las 23:37
-
Cierto, pero luego
const
no existia cuandostrlen
fue inventado, tampoco, pero no creo que podamos concluir de esto, questrlen
probablemente originalmente tomó cualquier parámetro que no sea un puntero de cadena 😉 Es solo que la forma típica de decir “una cadena” cambió.–Steve Jessop
26 de marzo de 2010 a las 1:42
-
Según el mismo documento, CPP no existía cuando Lesk escribió la biblioteca. Eso elimina el
open(2)
estilo de banderas ORed en un número entero, por lo que una cadena es una de las opciones menos malas. Mira mi respuesta.– Peter Cordes
10 de julio de 2018 a las 11:44
Siempre me ha molestado esto en la biblioteca C
– usuario16217248
4 sep 2022 a las 19:56