Stockage du caractère EOF (fin de fichier) dans un type de caractère

11

J'ai lu dans le livre du langage de programmation C de Dennis Ritchie qui intdoit être utilisé pour qu'une variable contienne EOF - pour la rendre suffisamment grande pour qu'elle puisse contenir une valeur EOF - non char. Mais le code suivant fonctionne bien:

#include<stdio.h> 

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

Lorsqu'il n'y a plus d'entrée, getcharrenvoie EOF. Et dans le programme ci-dessus, la variable c, de type char, est capable de la maintenir avec succès.

Pourquoi ça marche? Selon l'explication dans le livre mentionné ci-dessus, le code ne devrait pas fonctionner.

user1369975
la source
5
Ce code est susceptible d'échouer si vous lisez un caractère avec la valeur 0xff. Le stockage du résultat de getchar()dans intrésout ce problème. Votre question est essentiellement la même que la question 12.1 de la FAQ comp.lang.c , qui est une excellente ressource. (En outre, cela main()devrait l'être int main(void), et cela ne ferait pas de mal d'en ajouter un return 0;avant la clôture }.)
Keith Thompson
1
@delnan: L'article lié n'est pas tout à fait exact sur la façon dont Unix traite control-D. Il ne ferme pas le flux d'entrée; il fait simplement revenir tout fread () qui bloque sur la console avec toutes les données non encore lues. De nombreux programmes interprètent un retour à zéro octet de fread () comme indiquant EOF, mais le fichier restera en fait ouvert et capable de fournir plus d'entrée.
supercat

Réponses:

11

Votre code semble fonctionner, car les conversions de types implicites se produisent accidentellement pour faire la bonne chose.

getchar()renvoie un intavec une valeur qui correspond à la plage de unsigned charou est EOF(qui doit être négative, généralement -1). Notez que EOFlui - même n'est pas un caractère, mais un signal qu'il n'y a plus de caractères disponibles.

Lors de l'enregistrement du résultat depuis getchar()in c, il existe deux possibilités. Soit le type charpeut représenter la valeur, auquel cas il s'agit de la valeur de c. Ou le type char ne peut pas représenter la valeur. Dans ce cas, il n'est pas défini ce qui se passera. Les processeurs Intel coupent simplement les bits élevés qui ne rentrent pas dans le nouveau type (réduisant effectivement la valeur modulo 256 pour char), mais vous ne devriez pas vous fier à cela.

L'étape suivante consiste à comparer cavec EOF. Comme EOFc'est le cas int, csera également converti en un int, préservant la valeur stockée dans c. Si cpeut stocker la valeur de EOF, la comparaison réussira, mais si celle ne peut pas stocker la valeur, la comparaison échouera, car il y a eu une perte irrécupérable d'informations lors de la conversion EOFen type char.

Il semble que votre compilateur ait choisi de faire le chartype signé et la valeur de EOFsuffisamment petite pour tenir char. Si charvous n'aviez pas signé (ou si vous l'aviez utilisé unsigned char), votre test aurait échoué, car unsigned charil ne peut pas contenir la valeur de EOF.


Notez également qu'il existe un deuxième problème avec votre code. Comme ce EOFn'est pas un personnage lui-même, mais que vous le forcez dans un chartype, il y a très probablement un personnage qui est mal interprété comme étant EOFet pour la moitié des caractères possibles, il n'est pas défini s'ils seront traités correctement.

Bart van Ingen Schenau
la source
La contrainte de taper des charvaleurs en dehors de la plage CHAR_MIN.. CHAR_MAXvolonté est nécessaire pour produire une valeur définie par l'implémentation, produire un modèle de bits que l'implémentation définit comme une représentation d'interruption, ou élever un signal défini par l'implémentation. Dans la plupart des cas, les implémentations devraient passer par beaucoup de travail supplémentaire pour faire autre chose que la réduction du complément à deux. Si les membres du Comité des normes souscrivaient à l'idée que les compilateurs devraient être encouragés à mettre en œuvre des comportements compatibles avec ceux de la plupart des autres compilateurs en l'absence de raisons de faire autrement ...
supercat
... Je considérerais une telle coercition comme étant fiable (pour ne pas dire que le code ne devrait pas documenter ses intentions, mais cela (signed char)xdevrait être considéré comme plus clair et tout aussi sûr que ((unsigned char)x ^ CHAR_MAX+1))-(CHAR_MAX+1).) En l'état, je ne vois aucune probabilité de les compilateurs implémentant tout autre comportement conforme à la norme actuelle; le seul danger serait que la Norme soit modifiée pour rompre le comportement dans l'intérêt supposé de "l'optimisation".
supercat
@supercat: la norme est écrite de telle sorte qu'aucun compilateur ne doit produire de code dont le comportement n'est pas naturellement pris en charge par le processeur qu'il cible. La plupart des comportements indéfinis existent parce que (au moment de la rédaction de la norme), tous les processeurs ne se sont pas comportés de manière cohérente. Les compilateurs devenant plus matures, les rédacteurs de compilateurs ont commencé à tirer parti du comportement indéfini pour effectuer des optimisations plus agressives.
Bart van Ingen Schenau
Historiquement, l'intention de la norme était principalement celle que vous décrivez, bien que la norme décrive certains comportements de manière suffisamment détaillée pour exiger que les compilateurs de certaines plates-formes communes génèrent plus de code que ce qui serait requis dans le cadre d'une spécification plus lâche. La contrainte de type dans int i=129; signed char c=i;est l'un de ces comportements. Relativement peu de processeurs ont une instruction qui ferait la cmême ichose dans la plage de -127 à +127 et produirait un mappage cohérent d'autres valeurs de ià des valeurs dans la plage de -128 à +127 qui différaient de la réduction du complément à deux, ou. ..
supercat
... déclencherait systématiquement un signal dans de tels cas. Étant donné que la norme exige que les implémentations produisent un mappage cohérent ou lèvent un signal de manière cohérente, les seules plates-formes où la norme laisserait de la place à une réduction du complément à deux seraient des choses comme les DSP avec un matériel arithmétique saturant. En ce qui concerne la base historique du comportement indéfini, je dirais que le problème ne concerne pas uniquement les plates-formes matérielles. Même sur une plateforme où le débordement se comporterait de manière très cohérente, il peut être utile qu'un compilateur le piège ...
supercat