Differenza tra int e char in getchar / fgetc e putchar / fputc?

Sto cercando di imparare C da solo e sono un po ‘confuso con getchar e putchar :

1

 #include  int main(void) { char c; printf("Enter characters : "); while((c = getchar()) != EOF){ putchar(c); } return 0; } 

2

 #include  int main(void) { int c; printf("Enter characters : "); while((c = getchar()) != EOF){ putchar(c); } return 0; } 

La funzione di libreria C int putchar(int c) scrive un carattere (un char senza segno) specificato dall’argomento char su stdout.

La funzione di libreria C int getchar(void) ottiene un carattere (un char senza segno) da stdin. Questo è equivalente a getc con stdin come argomento.

Vuol dire che putchar() accetta sia int che char o uno di essi e per getchar() dovremmo usare un int o char ?

TL; DR:

  • char c; c = getchar(); è sbagliato, rotto e infestato .
  • int c; c = getchar(); è corretto

Questo vale anche per getc e fgetc , se non di più, perché spesso si legge fino alla fine del file.


Memorizza sempre il valore di ritorno di getchar ( fgetc , getc …) (e putchar ) inizialmente in una variabile di tipo int .

L’ argomento di putchar può essere qualsiasi di int , char , signed char o unsigned char ; il suo tipo non ha importanza e tutti funzionano allo stesso modo, anche se si potrebbe ottenere un numero positivo e altri in numeri interi negativi per i caratteri sopra e incluso \200 (128).


Il motivo per cui è necessario utilizzare int per memorizzare il valore di ritorno di getchar e putchar è che quando viene raggiunta la condizione di fine del file (o si verifica un errore I / O), entrambi restituiscono il valore della macro EOF che è una costante intera negativa (solitamente -1 ) .

Per getchar , se il valore di ritorno non è EOF , è il read unsigned char zero-extended a un int . Cioè, assumendo caratteri a 8 bit, i valori restituiti possono essere 0255 o il valore della macro EOF ; assumendo nuovamente char a 8 bit, non c’è modo di spremere questi 257 valori distinti in 256 in modo che ognuno di essi possa essere identificato in modo univoco.


Ora, se lo hai memorizzato in char , l’effetto dipenderà dal fatto che il tipo di carattere sia firmato o non firmato di default ! Questo varia dal compilatore al compilatore, dall’architettura all’architettura. Se char è firmato e supponendo che EOF sia definito come -1 , allora sia EOF che il carattere '\377' sull’input sarebbero paragonabili a EOF ; sarebbero estesi con il segno a (int)-1 .

D’altra parte, se char non è firmato (come per impostazione predefinita sui processori ARM, compresi i sistemi Raspberry PI , e sembra essere vero anche per AIX ), non c’è alcun valore che possa essere memorizzato in c che possa essere paragonato a -1 ; incluso EOF ; invece di scoppiare su EOF , il tuo codice genererebbe un singolo carattere \377 .

Il pericolo qui è che con i char firmati il ​​codice sembra funzionare correttamente anche se è ancora orribilmente rotto – uno dei valori di input legali viene interpretato come EOF . Inoltre, C89, C99, C11 non impone un valore per EOF ; dice solo che EOF è una costante intera negativa; quindi invece di -1 potrebbe anche dire -224 su una particolare implementazione, che farebbe sì che gli spazi si comportino come EOF .

gcc ha l’ -funsigned-char che può essere utilizzata per rendere il char non firmato su quelle piattaforms in cui è impostato come predefinito:

 % cat test.c #include  int main(void) { char c; printf("Enter characters : "); while((c= getchar()) != EOF){ putchar(c); } return 0; } 

Ora lo eseguiamo con il char firmato:

 % gcc test.c && ./a.out Enter characters : sfdasadfdsaf sfdasadfdsaf ^D % 

Sembra funzionare bene. Ma con char unsigned:

 % gcc test.c -funsigned-char && ./a.out Enter characters : Hello world Hello world                            ^C % 

Cioè, ho provato a premere Ctrl-D molte volte ma un stato stampato per ogni EOF invece di interrompere il ciclo.

Ora, ancora una volta, per il caso char firmato, non è ansible distinguere tra char 255 e EOF su Linux, spezzandolo per dati binari e così:

 % gcc test.c && echo -e 'Hello world\0377And some more' | ./a.out Enter characters : Hello world % 

Solo la prima parte fino \0377 stata scritta su stdout.


Fate attenzione che i confronti tra costanti di carattere e un int contenente il valore di carattere non firmato potrebbero non funzionare come previsto (ad esempio la costante di carattere 'ä' in ISO 8859-1 significherebbe il valore firmato -28 . Quindi assumendo che si scriva codice che legge input fino a 'ä' nella codepage ISO 8859-1, lo faresti

 int c; while((c = getchar()) != EOF){ if (c == (unsigned char)'ä') { /* ... */ } } 

A causa della promozione di interi, tutti i valori di char adattano a un int e vengono automaticamente promossi sulle chiamate di funzione, quindi puoi assegnare qualsiasi int , char , signed char o unsigned char a putchar come argomento (non per memorizzare il suo valore restituito), e funzionerebbe come previsto.

Il valore reale passato nel numero intero potrebbe essere positivo o addirittura negativo; per esempio la costante di carattere \377 sarebbe negativa su un sistema a 8 bit in cui il char è firmato; tuttavia putchar (o fputc realtà) fputc il valore in un char non firmato. C11 7.21.7.3p2 :

2 La funzione fputc scrive il carattere specificato da c (convertito in un char senza segno) nel stream di output indicato dallo stream […]

(sottolineatura mia)

fputc dire che fputc sarà garantito per convertire il dato c come se fosse (unsigned char)c

Usa sempre int per salvare il carattere da getchar() poiché la costante EOF è di tipo int . Se si utilizza char il confronto con EOF non è corretto.

Puoi tranquillamente passare char a putchar() anche se verrà promosso automaticamente a int .

Nota : Tecnicamente l’utilizzo di char funzionerà nella maggior parte dei casi, ma in questo caso non è ansible avere il carattere 0xFF in quanto verrà interpretato come EOF causa della conversione del tipo. Per coprire tutti i casi usa sempre int . Come dice @Ilja, int è necessario per rappresentare tutti i 256 possibili valori di carattere e l’ EOF , che è 257 possibili valori in totale, che non possono essere memorizzati nel tipo di char .