¿Diferencia entre int y char en getchar/fgetc y putchar/fputc?
Estoy intentando aprender C por mi cuenta y estoy un poco confundido con getchar
y putchar
:
1
#include <stdio.h>
int main(void)
{
char c;
printf("Enter characters : ");
while((c = getchar()) != EOF){
putchar(c);
}
return 0;
}
2
#include <stdio.h>
int main(void)
{
int c;
printf("Enter characters : ");
while((c = getchar()) != EOF){
putchar(c);
}
return 0;
}
La función de la biblioteca C int putchar(int c)
escribe un carácter (un carácter sin firmar) especificado por el argumento char en la salida estándar.
La función de la biblioteca C int getchar(void)
obtiene un carácter (un carácter sin firmar) de la entrada estándar. Esto es equivalente a getc con stdin como argumento.
¿Significa que putchar()
acepta ambos int
y char
o cualquiera de ellos y para getchar()
deberíamos usar un int
o char
?
TL;DR:
char c; c = getchar();
está mal, roto y con errores .int c; c = getchar();
es correcto .
Esto se aplica getc
también fgetc
, si no más, porque a menudo uno leería hasta el final del archivo.
Almacene siempre el valor de retorno de getchar
( fgetc
, getc
...) (y putchar
) inicialmente en una variable de tipo int
.
El argumento puede putchar
ser cualquiera de int
, char
o signed char
; unsigned char
su tipo no importa, y todos funcionan igual, aunque uno podría dar como resultado que se pasen enteros positivos y otros negativos para los caracteres superiores e incluidos \200
(128).
La razón por la que debes usar int
para almacenar el valor de retorno de ambos getchar
es putchar
que cuando se alcanza la condición de fin de archivo (o se produce un error de E/S), ambos devuelven el valor de la macro, EOF
que es un entero negativo. constante, (normalmente -1
) .
Porque getchar
, si el valor de retorno no es EOF
, es la lectura unsigned char
extendida a cero a un int
. Es decir, asumiendo caracteres de 8 bits, los valores devueltos pueden ser 0
... 255
o el valor de la macro EOF
; Suponiendo nuevamente un carácter de 8 bits, no hay forma de comprimir estos 257 valores distintos en 256 para que cada uno de ellos pueda identificarse de forma única.
Ahora, si lo almacenara en char
su lugar, el efecto dependería de si el tipo de carácter está firmado o sin firmar de forma predeterminada . Esto varía de un compilador a otro, de una arquitectura a otra. Si char
está firmado y suponiendo EOF
que se define como -1
, entonces tanto EOF
el carácter como '\377'
el de la entrada se compararían igual a EOF
; se extenderían con signos a (int)-1
.
Por otro lado, si char
no está firmado (como lo está de forma predeterminada en los procesadores ARM, incluidos los sistemas Raspberry PI ; y parece ser cierto también para AIX ), no hay ningún valor que pueda almacenarse c
que se compare igual a -1
; incluido EOF
; en lugar de dividirse en EOF
, su código generaría un solo \377
carácter.
El peligro aquí es que con los signos firmados char
el código parece funcionar correctamente aunque todavía esté terriblemente roto: uno de los valores de entrada legales se interpreta como EOF
. Además, C89, C99, C11 no exige un valor para EOF
; sólo dice que EOF
es una constante entera negativa; por lo tanto, en lugar de -1
eso, también podría decirse -224
sobre una implementación particular, lo que haría que los espacios se comportaran como EOF
.
gcc
tiene el interruptor -funsigned-char
que se puede usar para hacer que char
no esté firmado en aquellas plataformas donde está firmado de forma predeterminada:
% cat test.c
#include <stdio.h>
int main(void)
{
char c;
printf("Enter characters : ");
while ((c = getchar()) != EOF){
putchar(c);
}
return 0;
}
Ahora lo ejecutamos con firmado char
:
% gcc test.c && ./a.out
Enter characters : sfdasadfdsaf
sfdasadfdsaf
^D
%
Parece estar funcionando bien. Pero con sin firmar char
:
% gcc test.c -funsigned-char && ./a.out
Enter characters : Hello world
Hello world
���������������������������^C
%
Es decir, intenté presionar Ctrl-D
allí muchas veces pero �
se imprimió un para cada una EOF
en lugar de romper el bucle.
Ahora, nuevamente, para el char
caso firmado, no puede distinguir entre char
255 y EOF
en Linux, dividiéndolo en datos binarios y demás:
% gcc test.c && echo -e 'Hello world\0377And some more' | ./a.out
Enter characters : Hello world
%
Sólo la primera parte hasta el \0377
escape se escribió en la salida estándar.
Tenga en cuenta que las comparaciones entre constantes de caracteres y un int
valor que contiene el carácter sin firmar podrían no funcionar como se esperaba (por ejemplo, la constante de caracteres 'ä'
en ISO 8859-1 significaría el valor con signo -28
. Entonces, suponiendo que escriba código que lea la entrada hasta 'ä'
ISO 8859-1 página de códigos, lo harías
int c;
while ((c = getchar()) != EOF){
if (c == (unsigned char)'ä') {
/* ... */
}
}
Debido a la promoción de enteros, todos char
los valores caben en , y se promocionan automáticamente en las llamadas aint
funciones, por lo que puede dar cualquiera de int
, char
o signed char
to unsigned char
como putchar
argumento (no para almacenar su valor de retorno) y funcionaría como se esperaba.
El valor real pasado en el número entero puede ser positivo o incluso negativo; por ejemplo, la constante de caracteres \377
sería negativa en un sistema de caracteres de 8 bits donde char
está firmado; sin embargo putchar
(o fputc
en realidad) convertirá el valor a un carácter sin firmar. C11 7.21.7.3p2 :
2 La función fputc escribe el carácter especificado por
c
(convertido a un carácter sin signo) en el flujo de salida al que apunta el flujo [...]
(el énfasis es mío)
Es decir, se fputc
garantizará que convertirá lo dado c
como si fuera por(unsigned char)c
Úselo siempre int
para guardar el carácter ya getchar()
que la EOF
constante es de int
tipo. Si lo usa char
, la comparación EOF
no es correcta.
Sin embargo , puedes pasar char
a él con seguridad putchar()
, ya que se promocionará int
automáticamente.
Nota : Técnicamente, el uso char
funcionará en la mayoría de los casos, pero no puede tener el carácter 0xFF, ya que se interpretará como EOF
una conversión de tipo. Para cubrir todos los casos utilice siempreint
. Como lo expresó @Ilja, int
es necesario representar los 256 valores de caracteres posibles y , que EOF
son 257 valores posibles en total, que no se pueden almacenar en char
tipo.