tipos - ¿Es posible leer un archivo completo fseek() ing a SEEK_END y obtener el tamaño de archivo por ftell()?
manejo de archivos en programacion (1)
¿Tengo razón en que este código introduce un comportamiento indefinido?
#include <stdio.h>
#include <stdlib.h>
FILE *f = fopen("textfile.txt", "rb");
fseek(f, 0, SEEK_END);
long fsize = ftell(f);
fseek(f, 0, SEEK_SET); //same as rewind(f);
char *string = malloc(fsize + 1);
fread(string, fsize, 1, f);
fclose(f);
string[fsize] = 0;
La razón por la que estoy preguntando es que este código se publica como una respuesta aceptada y muy votada a la siguiente pregunta: C Programación: cómo leer todo el contenido del archivo en un búfer
Sin embargo, de acuerdo con el siguiente artículo: Cómo leer un archivo completo en la memoria en C ++ (que, a pesar de su título, también trata con C, así que quédate conmigo):
Supongamos que está escribiendo C, y tiene un
FILE*
(que conoce puntos a una secuencia de archivos, o al menos una secuencia buscable), y deseaba determinar cuántos caracteres asignar en un búfer para almacenar todo el contenido de la memoria. corriente. Tu primer instinto probablemente sea escribir un código como este:
// Bad code; undefined behaviour fseek(p_file, 0, SEEK_END); long file_size = ftell(p_file);
Parece legitimo. Pero luego empiezas a tener rarezas. En ocasiones, el tamaño informado es mayor que el tamaño real del archivo en el disco. Algunas veces es igual al tamaño real del archivo, pero la cantidad de caracteres que lee es diferente. ¿Qué diablos está pasando?
Hay dos respuestas, porque depende de si el archivo se ha abierto en modo texto o modo binario.
En caso de que no sepas la diferencia: en el modo predeterminado - modo de texto - en ciertas plataformas, ciertos caracteres se traducen de varias maneras durante la lectura. El más conocido es que en Windows, las líneas nuevas se traducen a
/r/n
cuando se escriben en un archivo, y se traducen a la inversa cuando se leen. En otras palabras, si el archivo contieneHello/r/nWorld
, se leerá comoHello/nWorld
; el tamaño del archivo es de 12 caracteres, el tamaño de la cadena es 11. Menos conocido es que0x1A
(oCtrl-Z
) se interpreta como el final del archivo, por lo que si el archivo contieneHello/x1AWorld
, se leerá comoHello
. Además, si la cadena en la memoria esHello/x1AWorld
y la escribe en un archivo en modo texto, el archivo seráHello
. En el modo binario, no se realizan traducciones: todo lo que está en el archivo se lee en su programa, y viceversa.Inmediatamente puede adivinar que el modo de texto va a ser un dolor de cabeza, al menos en Windows. De manera más general, según el estándar C:
La función
ftell
obtiene el valor actual del indicador de posición del archivo para la secuencia a la que apunta la transmisión. Para una secuencia binaria, el valor es la cantidad de caracteres desde el comienzo del archivo. Para una secuencia de texto, su indicador de posición de archivo contiene información no especificada, utilizable por la función fseek para devolver el indicador de posición de archivo para la secuencia a su posición en el momento de la llamada ftell; la diferencia entre estos dos valores de retorno no es necesariamente una medida significativa de la cantidad de caracteres escritos o leídos.En otras palabras, cuando se trata de un archivo abierto en modo texto, el valor que devuelve
ftell()
es inútil ... excepto en llamadas afseek()
. En particular, no necesariamente te dice cuántos personajes hay en el flujo hasta el punto actual.Por lo tanto, no puede usar el valor de retorno de
ftell()
para indicar el tamaño del archivo, el número de caracteres en el archivo o para nada (excepto en una llamada posterior afseek()
). Entonces no puedes obtener el tamaño del archivo de esa manera.De acuerdo, así que al demonio con el modo de texto. ¿Qué dicen que trabajamos solo en modo binario? Como dice el estándar C: "Para un flujo binario, el valor es el número de caracteres desde el comienzo del archivo". Eso suena prometedor
Y, de hecho, lo es. Si se encuentra al final del archivo y llama a
ftell()
, encontrará la cantidad de bytes en el archivo. ¡Hurra! ¡Éxito! Todo lo que tenemos que hacer ahora es llegar al final del archivo. Y para hacer eso, todo lo que necesitas hacer esfseek()
conSEEK_END
, ¿verdad?Incorrecto.
Una vez más, del estándar C:
Establecer el indicador de posición del archivo al final del archivo, como con
fseek(file, 0, SEEK_END)
, tiene un comportamiento indefinido para una secuencia binaria (debido a posibles caracteres nulos finales) o para cualquier secuencia con codificación dependiente del estado que no funciona Con seguridad termina en el estado de cambio inicial.Para comprender por qué este es el caso: algunas plataformas almacenan archivos como registros de tamaño fijo. Si el archivo es más corto que el tamaño del registro, el resto del bloque se rellena. Cuando busca el "fin", por razones de eficiencia, simplemente lo salta al final del último bloque ... posiblemente mucho después del final real de los datos, después de un montón de relleno.
Entonces, aquí está la situación en C:
- No puede obtener el número de caracteres con
ftell()
en modo texto.- Puede obtener el número de caracteres con
ftell()
en modo binario ... pero no puede buscar el final del archivo confseek(p_file, 0, SEEK_END)
.
No tengo suficiente conocimiento para juzgar quién está aquí, y si la respuesta aceptada anteriormente choca con este artículo, entonces estoy haciendo esta pregunta.
Lo que el autor del artículo omite maliciosamente es el contexto de la cita.
Del borrador del estándar n1570 de la C11, LA PIE DE PÁGINA NO NORMATIVA 268 :
Establecer el indicador de posición del archivo al final del archivo, como con fseek (archivo, 0, SEEK_END), tiene un comportamiento indefinido para una secuencia binaria (debido a posibles caracteres nulos finales) o para cualquier secuencia con codificación dependiente del estado que no funciona Con seguridad termina en el estado de cambio inicial.
La parte normativa de la norma que se refiere a la nota al pie es esta 7.21.3 Archivos :
9 Aunque las secuencias orientadas a texto y binarias de ancho son conceptualmente secuencias de caracteres anchos, el archivo externo asociado con una secuencia de orientación ancha es una secuencia de caracteres multibyte, generalizada de la siguiente manera:
- Las codificaciones multibyte dentro de los archivos pueden contener bytes nulos incorporados (a diferencia de las codificaciones multibyte válidas para uso interno del programa).
- No es necesario que un archivo comience ni termine en el estado de cambio inicial. 268)
Tenga en cuenta que esto se refiere a secuencias orientadas a ancho .
Ahora, en 7.21.9.2 La función fseek
3 Para una secuencia binaria, la nueva posición, medida en caracteres desde el comienzo del archivo, se obtiene sumando el desplazamiento a la posición especificada por el origen. La posición especificada es el comienzo del archivo si de dónde SEEK_SET, el valor actual del indicador de posición del archivo si SEEK_CUR o fin de archivo es SEEK_END. Un flujo binario no necesita soporte significativo para llamadas fseek con un valor de SEEK_END.
El lenguaje es una frase final mucho menos grave:
"No es necesario que un flujo binario soporte de manera significativa las llamadas fseek con un valor de SEEK_END".