Aïe aïe aïe...
J'ai voulu savoir comment il fallait vraiment manipuler getchar, et une maison m'est tombée sur la tête !
Tout ce qui suit s'applique à:
Code :
- getc (FILE*);
- getchar ( );
- fgetc (FILE*);
- fgetchar( ); //pas standard
- getch ( ); //pas standard
- getche ( ); //pas standard
|
Niveau 1
Il suffit de lire la documentation pour remarquer que le type de retour est int, et non pas char.
La raison est historique: à l'époque de ces fonctions antédilluviennes, toutes les fonctions renvoyaient le seul type existant: int.
Les types plus petits qu'un int étant promus en int pour la transmission, il n'y avait pas grand mal.
Et puis cela permettait de signaler les erreurs...
Niveau 2
Puisqu'un int dispose de plus de valeurs qu'un char, on en a profité pour spécifier une valeur de retour spéciale:
EOF, qui indique qu'une erreur est survenue, ou que la fin de l'entrée est atteinte.
Ce n'est pas un caractère, sa valeur n'est celle d'aucun caractère.
Une conséquence directe est que ceci est bogué:
Code :
- char c; //devrait être int
- while((c= getchar()) != EOF) //bogue
- /*...*/;
|
En effet, on compare à EOF non pas la valeur renvoyée par getchar, mais la valeur contenue dans c après affectation.
EOF n'étant pas représentable dans un char, ce code ne marche pas. Du moins ne devrait pas, certains compilateurs le faisant fonctionner à tort.
La solution est simple: c doit être un int.
Malheureusement, il y a pire...
Niveau 3
Le standard spécifie que le type char est distinct de signed char et unsigned char, et que chaque implémentation(1) est libre de le faire se comporter comme l'un ou l'autre. C'est pour leur permettre de choisir ce qui se manipule le mieux, et qui est le plus rapide.
Seulement, getchar & co. renvoient un code de caractère toujours positif, assimilable à un unsigned char. Probablement une raison historique...
Une conséquence directe est que ceci est bogué (bis):
Code :
- char c= getchar(); //bogue
|
Si char est non-signé, aucun problème.
Si char est signé, alors quoi ?
L'affectation est orientée sur la valeur arithmétique, et la préserve quand c'est possible.
Les règles exactes sont:
Citation :
Si la valeur source peut être représentée dans le type de destination, elle est inaltérée.
Autrement, si le type de destination est non-signé, la valeur est réduite via modulo U<type>_MAX+1.
Autrement le type de destination est signé et la valeur est dépendante de l'implémentation.
|
Donc, si la valeur renvoyéee par getchar est trop grande, le résultat est dépendant... Il peut même y avoir un signal de levé.
Dans la plupart des cas, les bits sont simplement copiés, mais ça n'est absolument pas garanti.
En particulier, il peut ne pas y avoir une correspondance réversible entre valeurs des version signed et unsigned d'un type:
Code :
- unsigned char uc1= getchar(); //ignorons EOF
- /**/ char c = uc1; //signé ?
- unsigned char uc2= c ;
- if(uc1==uc2); //peut être faux !
- if(uc1== (unsigned char)(char)uc1); //peut être faux !
|
Votre documentation est censée mentionner quelque part quelle transformation est effectuée. Sincèrement, je doute qu'elle le fasse.
C'est bien beau tout ça, mais alors on fait comment ?
Niveau 4
Ce qui est toujours bogué (ter):
Code :
- #include <limits.h>
- int i= getchar();
- char c1= i; //louvoiement inutile
- char c2= (char)i; //cast redondant
- char c3= i+CHAR_MIN; //arithmetique (optimisable)
- char c4= *(char *)&i; //aproximation du reinterpret_cast<char>(i) de C++
|
Stupides:
c1: L'int intermédiaire ne fait que retarder le problème.
c2: Le cast en char a de toutes façons lieu à l'affectation, c'est lui qui transforme la valeur.
c3: Pas mal.
Ça donne bien une valeur dans toute la gamme d'un char. C'est même optimisé si char est non-signé.
Mais... ce n'est pas forcément la valeur attendue par putchar, celle qui afficherait le caractère qui a été entré.
c4: Bien essayé...
Malheureusement, le codet(2) récupéré dépend du boutisme(3), ce n'est pas forcément celui qui nous intéresse.
Niveau 5
Ce qui devrait marcher:
Code :
- #include <limits.h>
- #include <stdio.h>
- #define EOF2 (CHAR_MIN-1)
- int getcharforreal(void){
- int i= getchar();
- if(i == EOF)
- return EOF2;
- {
- unsigned char uc = i; //écrétage des octets en trop
- char c= *(char*)&uc; //réprésentation binaire intacte
- return c; //cast en int
- }
- }
|
Et encore... Les conversions entre pointeurs de types différents ne sont pas garanties de marcher (sauf void*). Je ne sais pas si ça concerne même les variantes de signes...
EOF ne convient pas comme valeur de retour, car bien que ce ne soit pas une valeur de unsigned char, ça pourrait être valeur de char signé. D'ailleurs, ç'est très souvent -1.
Conclusion
Ça fait peur, non ?
En fait, il y a très peu de chances que tout ceci ai une quelconque importance pour vous.
On imagine mal un compilateur foirer un truc aussi simple que la lecture de caractères.
Mais, telle une épée de Damoclès, ça peut vous tomber dessus un jour.
A noter que les formats "%c" et "%s" de scanf et fscanf ne doivent logiquement pas souffrir de ce problème.
En effet, ils ignorent le type du pointeur reçu, à cause de la liste d'argument variable, et écrivent une représentation binaire sans même pouvoir se soucier de valeur arithmétique.
Cas du C++
Code :
- cin.get() ; //équivalent à getchar()
- cin.get(c); //ok
- cin >> c ; //ok
|
(1)implémentation:
Quasiment synonyme de compilateur.
(2)codet:
Mot français pour byte.
L'octet fait spécifiquement 8 bits, ce qui n'est pas toujours le cas du byte.
Le char du C++ défini comme un byte.
Donc, codet. Ou multiplet.
(3)boutisme:
Mot français pour endianness.
L'ordre dans lequel les octets d'une valeur sont placés en mémoire.
Autrement dit: par quel bout commence-t'on ?
Il existe aussi un boutisme pour l'ordre des bits.
Voilà, la visite guidée est terminée, je vous remercie de m'avoir suivi dans les tréfonds de la machinerie du C.
Message édité par Musaran le 14-10-2002 à 23:24:02
---------------
Bricocheap: Montage de ventilo sur paté de mastic silicone