¿Es posible confundir EOF con un valor de byte normal cuando se usa fgetc?

A menudo usamos fgetc así:

 int c; while ((c = fgetc(file)) != EOF) { // do stuff } 

Teóricamente, si un byte en el archivo tiene el valor de EOF , este código tiene errores: romperá el ciclo antes y no podrá procesar todo el archivo. ¿Es esta situación posible?

Según tengo entendido, fgetc internamente un byte leído desde el archivo a un unsigned char y luego a int , y lo devuelve. Esto funcionará si el rango de int es mayor que el de unsigned char .

¿Qué sucede si no es (probablemente entonces sizeof(int)=1 )?

  • ¿ fgetc leerá a fgetc un dato legítimo igual a EOF de un archivo?
  • ¿Alterará los datos que lee del archivo para evitar el valor único EOF ?
  • ¿Será fgetc una función no implementada?
  • ¿Será EOF de otro tipo, como long ?

Podría hacer que mi código fuera infalible con un cheque adicional:

 int c; for (;;) { c = fgetc(file); if (feof(file)) break; // do stuff } 

¿Es necesario si quiero máxima portabilidad?

Sí, c = fgetc(file); if (feof(file)) c = fgetc(file); if (feof(file)) funciona para la máxima portabilidad. Funciona en general y también cuando los caracteres unsigned char e int tienen el mismo número de valores únicos. Esto ocurre en plataformas raras con char , char signed char , unsigned char , short , unsigned short , int , unsigned , todo con el mismo ancho de bit y ancho de rango.

Tenga en cuenta que feof(file)) es insuficiente. El código también debe comprobar si hay ferror(file) .

 int c; for (;;) { c = fgetc(file); if (c == EOF) { if (feof(file)) break; if (ferror(file)) break; } // do stuff } 

La especificación de C dice que int debe ser capaz de mantener valores de -32767 a 32767 como mínimo. Cualquier plataforma con un int más pequeño no es estándar.

La especificación C también dice que EOF es una constante int negativa y que fgetc devuelve “un unsigned char convertido a int ” en el caso de una lectura exitosa. Dado que el unsigned char no puede tener un valor negativo, el valor de EOF se puede distinguir de cualquier cosa que se lea de la secuencia. *

* Vea a continuación un caso de escapatoria en el que esto no se cumple.


Texto estándar relevante (desde C99):

  • §5.2.4.2.1 Tamaños de tipos enteros :

    [Los] valores definidos por la implementación serán iguales o mayores en magnitud (valor absoluto) a los mostrados, con el mismo signo.

    […]

    • valor mínimo para un objeto de tipo int

      INT_MIN -32767

    • valor máximo para un objeto de tipo int

      INT_MAX

  • §7.19.1 – Introducción

    EOF … se expande a una expresión constante de tipo entero, con tipo int y un valor negativo, que son devueltas por varias funciones para indicar el final del archivo, es decir, no hay más entradas de un flujo

  • §7.19.7.1 La función fgets

    Si el indicador de fin de archivo para el flujo de entrada al que apunta el stream no está establecido y está presente el siguiente carácter, la función fgetc obtiene ese carácter como un unsigned char convertido a un int y avanza el indicador de posición de archivo asociado para el flujo (si está definido)

Si UCHAR_MAXINT_MAX , no hay problema: todos los valores de caracteres unsigned char se convertirán en enteros no negativos, por lo que serán distintos de EOF.

Ahora, hay una especie de laguna graciosa aquí: si un sistema tiene UCHAR_MAX > INT_MAX , entonces un sistema está legalmente autorizado para convertir valores mayores que INT_MAX en enteros negativos (según §6.3.1.3, el resultado de convertir un valor en un signo). el tipo que no puede representar ese valor se define en la implementación ), lo que hace posible que un carácter leído de un flujo se convierta a EOF.

CHAR_BIT > 8 sistemas con CHAR_BIT > 8 (p. Ej., El TI C4x DSP, que aparentemente usa bytes de 32 bits), aunque no estoy seguro de si están dañados con respecto a las funciones EOF y de flujo.

NOTA: la respuesta de chux es la correcta en el caso más general. Dejo esta respuesta porque creo que tanto la respuesta como la discusión en los comentarios son valiosas para comprender las situaciones (raras) en las que es necesario el enfoque de Chux.

Se garantiza que EOF tiene un valor negativo (C99 7.19.1) y, como mencionó, fgetc lee su entrada como un carácter sin signo antes de convertir a int. Así que esos por sí mismos garantizan que EOF no se puede leer desde un archivo.

En cuanto a sus preguntas específicas:

  • fgetc no puede leer un dato legítimo igual a EOF. En el archivo, no hay tal cosa como firmado o sin firmar; son solo secuencias de bits. Es C la que interpreta 1000 1111 de manera diferente dependiendo de si se trata como firmado o sin firmar. fgetc debe tratarlo como no firmado, por lo que los números negativos (que no sean EOF) no se pueden devolver.

    Anexo: No puede leer EOF para la parte char sin signo, pero cuando convierte el char sin signo en un int, si el int no es capaz de representar todos los valores del char sin signo, entonces el comportamiento está definido por la implementación (6.3. 1.3).

  • fgetc es requerido por el estándar para implementaciones alojadas, pero se permite que las implementaciones independientes omitan la mayoría de las funciones estándar de la biblioteca (algunas son aparentemente necesarias, pero no pude encontrar la lista).

  • EOF no requerirá mucho tiempo, ya que fgetc necesita poder devolverlo y fgetc devuelve un int.

  • En lo que respecta a la modificación de los datos, no puede cambiar el valor exactamente, pero dado que fgetc está especificado para leer “caracteres” del archivo en lugar de caracteres, podría leer en 8 bits a la vez, incluso si el sistema de lo contrario, se define que CHAR_BIT es 16 (que es el valor mínimo que podría tener si sizeof (int) == 1, dado que INT_MIN <= -32767 e INT_MAX> = 32767 se requieren para 5.2.4.2). En ese caso, el carácter de entrada se convertiría en un carácter sin signo que siempre tenía sus bits altos 0. Entonces podría hacer la conversión a int sin perder precisión. (En la práctica, esto simplemente no aparecerá, ya que las máquinas generalmente no tienen bytes de 16 bits)