Almacenar el carácter EOF (Fin de archivo) en un tipo char

11

Leí en el libro The C Programming Language de Dennis Ritchie que intdebe usarse para que una variable contenga EOF, para que sea lo suficientemente grande como para que pueda contener el valor EOF, no char. Pero el siguiente código funciona bien:

#include<stdio.h> 

main()  { 
  char c; 
  c=getchar(); 
  while(c!=EOF)  { 
    putchar(c); 
    c=getchar(); 
  } 
} 

Cuando no hay más entrada, getchardevuelve EOF. Y en el programa anterior, la variable c, con el tipo char, puede mantenerlo con éxito.

¿Por qué funciona esto? Según la explicación en el libro mencionado anteriormente, el código no debería funcionar.

usuario1369975
fuente
55
Es probable que este código falle si lee un carácter con el valor 0xff. Almacenar el resultado de getchar()un intresuelve ese problema. Su pregunta es esencialmente la misma que la pregunta 12.1 en las preguntas frecuentes de comp.lang.c , que es un excelente recurso. (Además, main()debería ser int main(void), y no estaría de más agregar un return 0;antes del cierre }.)
Keith Thompson,
1
@delnan: El artículo vinculado no es del todo correcto acerca de cómo Unix trata el control-D. No cierra el flujo de entrada; simplemente hace que cualquier fread () que está bloqueando en la consola regrese inmediatamente con cualquier información aún no leída. Muchos programas interpretan un retorno de cero bytes de fread () como indicativo de EOF, pero el archivo de hecho permanecerá abierto y podrá proporcionar más información.
supercat

Respuestas:

11

Parece que su código funciona, porque las conversiones de tipo implícito accidentalmente hacen lo correcto.

getchar()devuelve un intcon un valor que se ajusta al rango de unsigned charo es EOF(que debe ser negativo, por lo general es -1). Tenga en cuenta que en EOFsí mismo no es un carácter, sino una señal de que no hay más caracteres disponibles.

Al almacenar el resultado desde getchar()adentro c, hay dos posibilidades. El tipo charpuede representar el valor, en cuyo caso ese es el valor de c. O el tipo char no puede representar el valor. En ese caso, no está definido lo que sucederá. Los procesadores Intel simplemente cortan los bits altos que no encajan en el nuevo tipo (reduciendo efectivamente el valor del módulo 256 char), pero no debe confiar en eso.

El siguiente paso es comparar ccon EOF. Como EOFes un int, ctambién se convertirá en un int, conservando el valor almacenado en c. Si cpudiera almacenar el valor de EOF, entonces la comparación tendrá éxito, pero si noc puede almacenar el valor, entonces la comparación fallará, porque ha habido una pérdida irrecuperable de información al convertir a tipo .EOFchar

Parece que su compilador eligió charfirmar el tipo y el valor de lo EOFsuficientemente pequeño como para caber char. Si charno estuviera firmado (o si lo hubiera usado unsigned char), su prueba habría fallado, porque unsigned charno puede contener el valor de EOF.


También tenga en cuenta que hay un segundo problema con su código. Como EOFno es un personaje en sí mismo, pero lo fuerza a un chartipo, es muy probable que haya un personaje que se malinterprete como siendo EOFy para la mitad de los posibles personajes no está definido si se procesarán correctamente.

Bart van Ingen Schenau
fuente
La coerción para escribir charvalores fuera del rango CHAR_MIN... CHAR_MAXserá necesaria para generar un valor definido por la implementación, generar un patrón de bits que la implementación define como una representación de trampa o generar una señal definida por la implementación. En la mayoría de los casos, las implementaciones tendrían que pasar por mucho trabajo adicional para hacer algo más que la reducción del complemento a dos. Si la gente en el Comité de Normas se suscribieron a la idea de que los compiladores deben ser animados a poner en práctica comportamientos consistentes con la de la mayoría de los compiladores en ausencia de razones para hacer lo contrario ...
supercat
... Consideraría que tal coerción es confiable (por no decir que el código no debe documentar sus intenciones, pero eso (signed char)xdebería considerarse más claro y tan seguro como ((unsigned char)x ^ CHAR_MAX+1))-(CHAR_MAX+1)). Tal como es, no veo ninguna probabilidad de compiladores que implementan cualquier otro comportamiento que cumpla con el estándar actual; El único peligro sería que el estándar podría cambiarse para romper el comportamiento en el supuesto interés de "optimización".
supercat
@supercat: el estándar está escrito de tal manera que ningún compilador tiene que producir código que tenga un comportamiento que no sea naturalmente compatible con el procesador al que se dirige. La mayor parte del comportamiento indefinido existe porque (al momento de escribir el estándar) no todos los procesadores se comportaron de manera consistente. Con los compiladores cada vez más maduros, los escritores de compiladores han comenzado a aprovechar el comportamiento indefinido para hacer optimizaciones más agresivas.
Bart van Ingen Schenau
Históricamente, la intención del Estándar era principalmente la que usted describe, aunque el Estándar describe algunos comportamientos con suficiente detalle como para requerir que los compiladores de algunas plataformas comunes generen más código del que se requeriría en una especificación más flexible. El tipo de coerción int i=129; signed char c=i;es uno de esos comportamientos. Relativamente pocos procesadores tienen una instrucción que cigualaría icuando está en el rango de -127 a +127 y produciría un mapeo consistente de otros valores de ia valores en el rango de -128 a +127 que difieren de la reducción del complemento a dos, o. ..
supercat
... constantemente emitiría una señal en tales casos. Dado que el Estándar requiere que las implementaciones produzcan un mapeo consistente o generen constantemente una señal, las únicas plataformas donde el Estándar dejaría espacio para algo más que la reducción del complemento a dos serían cosas como DSP con hardware de aritmética saturada. En cuanto a la base histórica del comportamiento indefinido, diría que el problema no es solo con las plataformas de hardware. Incluso en una plataforma donde desbordamiento se comportarían de una manera muy consistente, puede ser útil disponer de un compilador atraparlo ...
supercat