¿Cuál es la razón fundamental para que fread / fwrite tome tamaño y cuente como argumentos?

96

Tuvimos una discusión aquí en el trabajo sobre por qué fread y fwrite toman un tamaño por miembro y cuentan y devuelven el número de miembros leídos / escritos en lugar de simplemente tomar un búfer y un tamaño. El único uso que se nos puede ocurrir es si desea leer / escribir una matriz de estructuras que no son divisibles por la alineación de la plataforma y, por lo tanto, se han rellenado, pero que no pueden ser tan comunes como para justificar esta elección. en diseño.

Desde FREAD (3) :

La función fread () lee nmemb elementos de datos, cada tamaño en bytes, del flujo al que apunta el flujo, almacenándolos en la ubicación dada por ptr.

La función fwrite () escribe nmemb elementos de datos, cada tamaño en bytes, en el flujo al que apunta el flujo, obteniéndolos de la ubicación dada por ptr.

fread () y fwrite () devuelven el número de elementos leídos o escritos correctamente (es decir, no el número de caracteres). Si se produce un error o se llega al final del archivo, el valor devuelto es un recuento corto de elementos (o cero).

David Holm
fuente
10
oye, esta es una buena pregunta. Siempre me pregunté al respecto
Johannes Schaub - litb

Respuestas:

22

Se basa en cómo se implementa fread .

La Especificación Única de UNIX dice

Para cada objeto, se realizarán llamadas de tamaño a la función fgetc () y los resultados se almacenarán, en el orden de lectura, en una matriz de caracteres sin firmar que se superpongan exactamente al objeto.

fgetc también tiene esta nota:

Dado que fgetc () opera en bytes, la lectura de un carácter que consta de varios bytes (o "un carácter de varios bytes") puede requerir múltiples llamadas a fgetc ().

Por supuesto, esto es anterior a las sofisticadas codificaciones de caracteres de bytes variables como UTF-8.

El SUS señala que esto en realidad se toma de los documentos ISO C.

Señor de poder
fuente
72

La diferencia en fread (buf, 1000, 1, stream) y fread (buf, 1, 1000, stream) es que, en el primer caso, solo obtiene un fragmento de 1000 bytes o nuthin, si el archivo es más pequeño y en el En segundo caso, todo lo que hay en el archivo es menor y hasta 1000 bytes.

Peter Miehle
fuente
4
Aunque es cierto, eso solo cuenta una pequeña parte de la historia. Sería mejor contrastar algo leyendo, digamos, una matriz de valores int, o una matriz de estructuras.
Jonathan Leffler
3
Esta sería una gran respuesta si se completara la justificación.
Matt Joiner
13

Esto es pura especulación, sin embargo, en el pasado (algunos todavía existen) muchos sistemas de archivos no eran simples flujos de bytes en un disco duro.

Muchos sistemas de archivos se basaban en registros, por lo que para satisfacer dichos sistemas de manera eficiente, deberá especificar el número de elementos ("registros"), permitiendo que fwrite / fread opere en el almacenamiento como registros, no solo como flujos de bytes.

nos
fuente
1
Me alegro de que alguien haya mencionado esto. Trabajé mucho con las especificaciones del sistema de archivos y FTP y los registros / páginas y otros conceptos de bloqueo tienen un soporte muy firme, aunque ya nadie usa esas partes de las especificaciones.
Matt Joiner
9

Aquí, déjame arreglar esas funciones:

size_t fread_buf( void* ptr, size_t size, FILE* stream)
{
    return fread( ptr, 1, size, stream);
}


size_t fwrite_buf( void const* ptr, size_t size, FILE* stream)
{
    return fwrite( ptr, 1, size, stream);
}

En cuanto a la justificación de los parámetros de fread()/ fwrite(), perdí mi copia de K&R hace mucho tiempo, así que solo puedo adivinar. Creo que una respuesta probable es que Kernighan y Ritchie simplemente pensaron que la realización de E / S binarias se haría de forma más natural en matrices de objetos. Además, es posible que hayan pensado que la E / S en bloque sería más rápida / fácil de implementar o lo que sea en algunas arquitecturas.

Aunque el estándar C especifica eso fread()y fwrite()debe implementarse en términos de fgetc()y fputc(), recuerde que el estándar entró en existencia mucho después de que K&R definiera C y que las cosas especificadas en el estándar podrían no haber estado en las ideas originales de los diseñadores. Incluso es posible que las cosas que se dicen en "El lenguaje de programación C" de K&R no sean las mismas que cuando se diseñó el lenguaje por primera vez.

Finalmente, esto es lo que PJ Plauger tiene que decir fread()en "The Standard C Library":

Si el size(segundo) argumento es mayor que uno, no puede determinar si la función también lee size - 1caracteres adicionales más allá de lo que informa. Como regla, es mejor llamar a la función como en fread(buf, 1, size * n, stream);lugar de fread(buf, size, n, stream);

Básicamente, está diciendo que fread()la interfaz está rota. Porque fwrite()señala que, "los errores de escritura son generalmente raros, por lo que esto no es un defecto importante", una afirmación con la que no estaría de acuerdo.

Michael Burr
fuente
17
En realidad, a menudo me gusta hacerlo al revés: fread(buf, size*n, 1, stream);si las lecturas incompletas son una condición de error, es más sencillo disponer freadque simplemente devuelva 0 o 1 en lugar de la cantidad de bytes leídos. Luego, puede hacer cosas como en if (!fread(...))lugar de tener que comparar el resultado con la cantidad solicitada de bytes (que requiere código C adicional y código de máquina adicional).
R .. GitHub DEJA DE AYUDAR A ICE
1
@R .. Solo asegúrate de comprobar que size * count! = 0 además de! Fread (...). Si size * count == 0, obtiene un valor de retorno cero en una lectura exitosa (de cero bytes), feof () y ferror () no se establecerán, y errno será algo sin sentido como ENOENT, o peor , algo engañoso (y posiblemente crítico) como EAGAIN - muy confuso, especialmente porque básicamente ninguna documentación grita esto te atrapó.
Pegasus Epsilon
3

Probablemente se remonta a la forma en que se implementó la E / S de archivo. (en el pasado) Podría haber sido más rápido escribir / leer archivos en bloques que escribir todo a la vez.

Dolch
fuente
Realmente no. La especificación C para fwrite señala que realiza llamadas repetidas a fputc: opengroup.org/onlinepubs/009695399/functions/fwrite.html
Powerlord
1

Tener argumentos separados para el tamaño y el recuento podría ser ventajoso en una implementación que puede evitar leer registros parciales. Si se usaran lecturas de un solo byte de algo como una tubería, incluso si se usaran datos de formato fijo, se tendría que permitir la posibilidad de que un registro se divida en dos lecturas. En cambio, si pudiera solicitar, por ejemplo, una lectura sin bloqueo de hasta 40 registros de 10 bytes cada uno cuando hay 293 bytes disponibles, y hacer que el sistema devuelva 290 bytes (29 registros completos) dejando 3 bytes listos para la siguiente lectura, eso sería será mucho más conveniente.

No sé hasta qué punto las implementaciones de fread pueden manejar tal semántica, pero ciertamente podrían ser útiles en implementaciones que podrían prometer respaldarlas.

Super gato
fuente
@PegasusEpsilon: Si, por ejemplo, un programa lo hace fread(buffer, 10000, 2, stdin)y el usuario escribe newline-ctrl-D después de escribir 18,000 bytes, sería bueno si la función pudiera devolver los primeros 10,000 bytes mientras deja los 8,000 restantes pendientes para futuras solicitudes de lectura más pequeñas, pero ¿están ahí? ¿Alguna implementación donde eso sucedería? ¿Dónde se almacenarían los 8.000 bytes en espera de esas solicitudes futuras?
supercat
Después de haberlo probado, resulta que fread () no funciona en lo que yo consideraría la forma más conveniente en este sentido, pero luego volver a rellenar bytes en el búfer de lectura después de determinar una lectura corta es probablemente un poco más de lo que deberíamos esperar. funciones de biblioteca estándar de todos modos. fread () leerá registros parciales y los guardará en el búfer, pero el valor de retorno especificará cuántos registros completos se han leído y no le dice nada (lo cual es bastante molesto para mí) sobre las lecturas cortas extraídas de stdin.
Pegasus Epsilon
... continúa ... Lo mejor que puede hacer es probablemente llenar su búfer de lectura con nulos antes de fread, y verificar el registro después de donde fread () dice que terminó en busca de bytes no nulos. No te ayuda particularmente cuando tus registros pueden contener nulos, pero si vas a usar sizemás de 1, bueno ... Para el registro, también puede haber ioctls u otras tonterías que puedes aplicar a la transmisión para hacerlo comportarse de manera diferente, no he profundizado tanto.
Pegasus Epsilon
También borré mi comentario anterior debido a una inexactitud. Oh bien.
Pegasus Epsilon
@PegasusEpsilon: C se usa en muchas plataformas, que se adaptan a diferentes comportamientos. La noción de que los programadores deberían esperar usar las mismas características y garantías en todas las implementaciones ignora lo que había sido la mejor característica de C: que su diseño permitiría a los programadores usar características y garantías en plataformas donde estuvieran disponibles. Algunos tipos de transmisiones pueden admitir retrocesos de tamaño arbitrario fácilmente, y tener el freadtrabajo que describió en tales transmisiones sería útil si hubiera alguna forma de identificar las transmisiones que funcionan de esa manera.
supercat
0

Creo que es porque C carece de sobrecarga de funciones. Si hubiera alguno, el tamaño sería redundante. Pero en C no puede determinar el tamaño de un elemento de matriz, debe especificar uno.

Considera esto:

int intArray[10];
fwrite(intArray, sizeof(int), 10, fd);

Si fwrite acepta el número de bytes, puede escribir lo siguiente:

int intArray[10];
fwrite(intArray, sizeof(int)*10, fd);

Pero es simplemente ineficiente. Tendrá un tamaño de (int) veces más llamadas al sistema.

Otro punto que debe tenerse en cuenta es que normalmente no desea que una parte de un elemento de matriz se escriba en un archivo. Quieres el entero entero o nada. fwrite devuelve una serie de elementos escritos con éxito. Entonces, si descubre que solo se escriben 2 bytes bajos de un elemento, ¿qué haría?

En algunos sistemas (debido a la alineación) no puede acceder a un byte de un entero sin crear una copia y cambiar.

Vanuan
fuente