Il tuo codice sembra funzionare, perché le conversioni di tipo implicito accidentalmente capita di fare la cosa giusta.
getchar() restituisce un int con un valore che rientra nell'intervallo di unsigned char o è EOF (che deve essere negativo, di solito è -1). Nota che EOF non è un personaggio, ma segnala che non ci sono più caratteri disponibili.
Quando si memorizza il risultato da getchar() in c , ci sono due possibilità. O il tipo char può rappresentare il valore, nel qual caso è il valore di c . Oppure il tipo char non può rappresentare il valore. In tal caso, non è definito cosa accadrà. I processori Intel tagliano solo i bit alti che non si adattano al nuovo tipo (riducendo efficacemente il valore modulo 256 per char ), ma non dovresti fare affidamento su questo.
Il prossimo passo è confrontare c con EOF . Poiché EOF è un int , c verrà convertito anche in int , preservando il valore memorizzato in c . Se c potrebbe memorizzare il valore di EOF , allora il confronto avrà successo, ma se c potrebbe non memorizzare il valore, allora il confronto fallirà, perché c'è stata una perdita irrecuperabile di informazioni durante la conversione di EOF nel tipo char .
Sembra che il compilatore abbia scelto di rendere il tipo char firmato e il valore di EOF abbastanza piccolo da rientrare in char . Se char non era firmato (o se hai utilizzato unsigned char ), il tuo test sarebbe fallito, perché unsigned char non può contenere il valore di EOF .
Nota anche che c'è un secondo problema con il tuo codice. Poiché EOF non è un personaggio stesso, ma lo imponi in un tipo char , è molto probabile che un personaggio là fuori venga interpretato erroneamente come EOF e per metà dei possibili caratteri non è definito se saranno elaborato correttamente.