Estoy tratando de 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 biblioteca C int putchar(int c)
escribe un carácter (un carácter sin signo) especificado por el argumento char en stdout.
La función de biblioteca C int getchar(void)
obtiene un carácter (un carácter sin firmar) de stdin. Esto es equivalente a getc con stdin como argumento.
Eso significa putchar()
acepta ambos int
y char
o cualquiera de ellos y para getchar()
¿Deberíamos usar un int
o char
?
TL;RD:
char c; c = getchar();
es mal, roto y con errores.
int c; c = getchar();
es correcto.
Esto aplica a getc
y fgetc
también, si no más, porque a menudo se leía hasta el final del archivo.
Guarde siempre el valor de retorno de getchar
(fgetc
, getc
…) (y putchar
) inicialmente en una variable de tipo int
.
Él argumento para putchar
puede ser cualquiera de int
, char
, signed char
o unsigned char
; su tipo no importa, y todos funcionan de la misma manera, aunque uno puede dar como resultado números enteros positivos y otros negativos para los caracteres superiores e incluidos \200
(128).
la razon por la que tu deber usar int
para almacenar el valor devuelto de ambos getchar
y putchar
es 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 una constante entera negativa, (generalmente -1
).
Para getchar
si el valor de retorno no es EOF
es la lectura unsigned char
cero extendido 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 de nuevo 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 almacenaste en char
en cambio, el efecto dependería de si el tipo de carácter está firmado o no firmado por defecto. Esto varía de un compilador a otro, de una arquitectura a otra. Si char
está firmado y asumiendo EOF
Se define como -1
entonces ambas cosas EOF
y carácter '\377'
en la entrada se compararía igual a EOF
; se extenderían por signo a (int)-1
.
Por otro lado, si char
no está firmado (como lo es de forma predeterminada en los procesadores ARM, incluido Sistemas de frambuesa PI; y parece ser cierto para AIX también), hay no valor que podría ser almacenado en c
que compararía igual a -1
; incluido EOF
; en lugar de estallar en EOF
su código generaría un solo \377
personaje.
El peligro aquí es que con firmado char
es el codigo parece estar funcionando correctamente a pesar de que 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
; solo dice eso EOF
es una constante entera negativa; así en lugar de -1
bien podría decirse -224
en una implementación particular, lo que haría que los espacios se comportaran como EOF
.
gcc
tiene el interruptor -funsigned-char
que se puede utilizar para hacer la char
sin firmar en aquellas plataformas donde por defecto está firmado:
% 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 sin firmar char
:
% gcc test.c -funsigned-char && ./a.out
Enter characters : Hello world
Hello world
���������������������������^C
%
Es decir, traté de presionar Ctrl-D
allí muchas veces pero un �
se imprimió para cada EOF
en lugar de romper el bucle.
Ahora, de nuevo, para el firmado char
caso, no puede distinguir entre char
255 y EOF
en Linux, rompiéndolo para datos binarios y tal:
% gcc test.c && echo -e 'Hello world\0377And some more' | ./a.out
Enter characters : Hello world
%
Sólo la primera parte hasta la \0377
escape fue escrito en stdout.
Tenga en cuenta que las comparaciones entre constantes de caracteres y un int
que contiene el valor de carácter sin signo podría no funcionar como se esperaba (por ejemplo, la constante de carácter 'ä'
en ISO 8859-1 significaría el valor firmado -28
. Entonces, suponiendo que escriba un código que lea la entrada hasta 'ä'
en la página de códigos ISO 8859-1, haría
int c;
while ((c = getchar()) != EOF){
if (c == (unsigned char)'ä') {
/* ... */
}
}
Debido a la promoción de enteros, todos char
los valores encajan en un int
y se promocionan automáticamente en llamadas de función, por lo que puede dar cualquiera de int
, char
, signed char
o unsigned char
para putchar
como un argumento (no para almacenar su valor de retorno), y funcionaría como se esperaba.
El valor real pasado en el entero puede ser positivo o incluso negativo; por ejemplo la constante de carácter \377
sería negativo en un sistema de caracteres de 8 bits donde char
está firmado; sin embargo putchar
(o fputc
en realidad) convertirá el valor en 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 char sin firmar) al flujo de salida apuntado por flujo […]
(énfasis mío)
es decir, el fputc
estará garantizado para convertir el dado c
como si por (unsigned char)c
Siempre usa int
para guardar el personaje de getchar()
como EOF
constante es de int
tipo. Si utiliza char
entonces la comparación contra EOF
no es correcto.
Puedes pasar con seguridad char
para putchar()
aunque como será promovido a int
automáticamente.
Nota: técnicamente utilizando char
funcionará en la mayoría de los casos, pero no puede tener el carácter 0xFF, ya que se interpretará como EOF
debido a la conversión de tipo. Para cubrir todos los casos siempre usar int
. Como dijo @Ilja: int
es necesario para representar los 256 valores de caracteres posibles y la EOF
que son 257 valores posibles en total, que no se pueden almacenar en char
tipo.
¿Por qué la variable utilizada para contener el valor de retorno de getchar debe declararse como int?
– phuclv
15 mayo 2017 a las 12:15
Posible duplicado de ¿Por qué la variable utilizada para contener el valor de retorno de getchar debe declararse como int?
– phuclv
15 mayo 2017 a las 12:15
@LưuVĩnhPhúc o al revés. La edad de una pregunta no importa
– Antti Haapala — Слава Україні
19 mayo 2017 a las 10:05