Voici le passage du cours auquel se réfère ma question : chapitre "manipulations basiques des entrée sorties".
Pour l’utilisation de scanf, il est dit qu’on utilise l’indicateur %hhd pour récupérer un signed char. Je fais donc ce test basique :
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
char x, y; //j'ai aussi tenté avec signed char et unsigned char
scanf("%hhd %hhd", &x, &y); //j'entre 45 46
printf("x = %d | y = %d\n", x, y); //affiche 0 | 46 (pas logique ?)
return 0;
}
Le cours commet-il une erreur ? (je suis bien au fait de la complexité de scanf et de l’impossibilité de donner un aperçu exhaustif de son fonctionnement au début d’un cours pour débutants, mais on sait jamais).
Chez moi ça marche™.
Je ne vois a priori aucune erreur dans le code ni aucune raison pour que tu obtiennes ce résultat.
As-tu vérifié le retour de scanf(), à tout hasard ?
Edit : en fait, le comportement fait penser à un buffer overflow, comme si scanf() écrivait plus qu’un byte. Vu que les variables x et y se suivent dans la pile et que ta machine est très probablement petit boutiste, si scanf() tente d’écrire 46 représenté sur plus d’un byte (par exemple deux avec 0010 1110 0000 0000), alors la valeur de x est remplacée par zéro. Reste à savoir pourquoi ton implémentation de scanf() se comporte ainsi. Sur quel OS compiles-tu et avec quel compilateur ?
Oula ça a l’air compliqué tout ça haha !
Je suis sur Windows 64bits, processeur x64, je compile avec gcc en ligne de commande.
Voici la ligne de compilation : gcc *.c -o exe (oui pas de warning et tout je sais c’est pas bien du tout ! mais c’est juste pour faire des petits tests).
Sinon, je ne sais pas si mon gcc est en 32 ou 64 bits…
Quel est le terminal que tu utilises pour exécuter ton programme ?
Je suspecte le caractère espace que tu entres de ne pas en être vraiment un.
Si j’essaie d’exécuter le programme avec 45 46 en entrée tout fonctionne bien, mais si je remplace l’espace par une espace insécable j’obtiens le même comportement que toi par exemple (x vaut 45 mais y vaut 0).
@Gil Cot : quel est le but des deux tests exactement ? Le problème vient visiblement de scanf(), pas de printf() ou de l’encodage des caractères.
@entwanne : dans l’exemple donné c’est x qui est à zéro et y qui est à 46. Ce serait toujours intéressant d’avoir la valeur de retour de scanf(), cela dit c’est quasiment certain qu’elle sera de 2 et qu’il lit bien les deux nombres vu qu'y vaut 46.
@AScriabine : comme demandé par @Gil Got, ce serait intéressant d’avoir ta version de gcc et aussi de savoir comment tu l’as installé (via Msys2, via Code::Blocks ou un autre IDE ou une installation directe de MinGW ?) car cela détermine quelle bibliothèque C tu utilises.
Il faut bien se rendre compte qu’il n’y a pas de souci d’encodage (cf. le commentaire « j’ai aussi tenté avec signed char et unsigned char » dans le code initial) ; Et je n’accuse point printf() pas plus que scanf() qui n’a certainement pas de souci… (mais ça je comptais le constater par deux petits tests aussi.)
@Gil Cot : je ne saisis pas ton explication. L’entrée est composée de deux nombres, or les chiffres ne posent pas de problèmes d’encodage : ils ont le même point de code (0x30 à 0x39) dans toutes les tables : ASCII, IMB850, UNICODE, etc. La seule exception serait une entrée en UTF-16 ou en UTF-32, mais la présence de bytes nuls ne produirait pas la sortie présentée, peu importe le boutisme.
Cependant, je ne crois pas que ce soit le vrai problème.
J’ai déjà été sur Windows 8.1 avec MinGw-W64 à la version 8.3 et je n’ai jamais eu ce genre de problème.
Je te suggère encore de faire des tests avec fgets.
@PierrotLeFou : comme dit précédemment, ce n’est pas lié à l’entrée, elle est correcte.
Ok, le problème est alors probablement effectivement là.
Je n’ai pas les détails techniques sous-jacent ne connaissant pas suffisamment Windows, mais pour faire bref, ton programme utilise actuellement une implémentation de la bibliothèque C qui n’est pas conforme à la norme C99 (l’indicateur de conversion hhd ayant été introduit par celle-ci).
Tu as 2 solutions sous Windows (en plus de la machine virtuelle ou de WSL, mais dans ce cas ce n’est pas de la compilation sous Windows) pour disposer d’une bibliothèque standard à jour :
Utiliser Msys2 qui compense ce qui manque de base sous Windows en liant ton programme à une bibliothèque spécifique, libmsys2 ;
Installer Visual Studio Community 2022 et utiliser son compilateur C à la place de MinGW.
Edit : comme le suggère @PierrotLeFou, installer une version plus récente de MinGW corrige peut-être également le problème.
A priori, la version actuelle de scanf() que tu emploies interprète l’indicateur hhd comme hd. On peur vérifié ça aisément avec le code ci-dessous.
#include<stdio.h>intmain(void){
int x = 0x11111111, y = 0x11111111;
scanf("%hhd %hhd", &x, &y);
printf("%08x, %08x\n", x, y);
return0;
}
Si tu fournis en entrée 45 46 et obtiens en sortie 1111002d 1111002e alors c’est bien que scanf() utilises des short int et non des signed char. Tu peux aussi simplement tester ton code initial en changeant le type des variables x et y par short.
(Oui, ce code ne respecte techniquement pas la norme, mais chut. )
Comme l’a dit @dalfab, l’exécution du code montre que l’indicateur hhd est utilisé comme d (donc scanf() stocke la valeur dans un int).
Concernant le respect de la norme1, celle-ci impose qu’un objet ne peut être modifié que via une lvalue (la norme défini une lvalue comme une expression désignant un objet) qui :
a le même type ;
a une version qualifiée du même type (genre const int) ;
a une version non signée ou signée du même type ;
est une union ou une structure dont un des membres respecte un des trois critères précédents ;
est de type char, signed char ou unsigned char.
En très gros, dans l’exemple que je donnais, je partais du principe que scanf() allait interprété l’indicateur hhd comme hd. Ce qui veux dire que scanf() allait interpréter les deux pointeurs que je lui passe comme des pointeurs sur short alors que ce sont des pointeurs sur int. Foncièrement cela revient au même que d’écrire ceci.
int x = 0x11111111, y = 0x11111111;
int *p = (short *)&x, *q = (short *)&y;
*p = 45; /* Interdit */
*q = 46; /* Interdit */
Ce code est invalide car j’accède à un objet de type int via une lvalue (ici *p et *q) de type short. Note que je parle bien d’accès, même si je lisais simplement le contenu ce serait également invalide.
En revanche, si hhd est interprété comme d c’est correct car le type est identique et si hhd est interprété comme char (signé ou non signé) c’est correct également (dernier point de la liste).
ISO/IEC 9899:2017, N2310, § 6.5 Expressions, al. 7, pp. 55–56.↩
Sorry, j’ai été absent par ici (sur d’autres fronts)
Alors, outre le problème explicitement posé (auquel je ne réponds qu’à la fin en m’inquiétant de la version de GCC), j’ai noté qu’il y a un point à éclaircir (et puis c’est toujours mieux de pouvoir se concentrer sur un bogue sans plusieurs trucs qui parasitent.)
Je fais référence au passage suivant…
…mais effectivement, mes tests sont un peu mal choisis (point de code inférieur à 126)
Un truc que beaucoup ne saisissent pas en se mettant au C, c’est que le type char n’est pas un vrai type au même sens que int ou short mais juste l’indication qu’on stocke un octet représentant normalement (mais pas obligatoirement) le code d’un caractère… L’autre point, une fois ceci compris, est que par défaut les types entiers sont signés par défaut (quand je mets juste int c’est synonyme de signed int) alors que le pseudo-type caractère c’est souvent1 l’inverse et cela fait sens (puisqu’on utilise tout l’octet… en fait quand on ne prend pas en compte le premier bit, comme on le ferait si c’est un bit de signe, on se retrouve en train d’utiliser explicitement de l’ASCII i.e. 7 bits)
1 Je veux dire que quand je mets juste char c’est souvent synonyme de unsigned char …Mais attention que c’est en réalité implementation-defined sauf dans deux cas : quand char et sort ont la même taille, alors char tout court doit être signé ; quand le jeu de caractères requis par C sur la plateforme cible map avec des codes dépassant la plus grande valeur de signed char alors char tout court doit être non signé.
En relisant l’échange, je crois comprendre que j’ai laissé comprendre que le souci pourrait être l’encodage ou au niveau de printf() ? En terminant par
L’idée était qu’il fallait être bien au fait des données qu’on manipule, d’où le clin d’œil
…et une fois que ceci n’est plus un sujet, on pouvait en revenir à nos moutons
Ce point a été traité, par toi, entre temps : bien comprendre la différence entre %d et %hd et %hhd
Il se trouve, accessoirement, que du fait la vieille version de compilateur utilisée, %hhd était compris comme %d
Ils ne sont pas « un peu mal choisis », ils sont inutiles, ils n’apportent rien au problème posé. Tu n’as d’ailleurs toujours pas expliqué leur utilité en trois messages successifs (celui-ci inclus).
Sinon, je ne sais pas où tu veux en venir avec tes histoires de « le type char n’est pas un vrai type », ce que je sais en revanche c’est que la norme le défini bien comme tel.
Par ailleurs, en ce qui me concerne, je n’ai jamais vu un code où le type char est un synonyme de unsigned char. En un sens on s’en fout, vu que comme tu le précises, le choix du caractère signé ou non du type char est laissé au soin du compilateur, mais dire que « c’est souvent synonyme de unsigned char » est un abus de langage.
Sauf que, une fois encore, tu n’expliques pas en quoi cela serait un problème d’encodage (au sens : d’où déduis-tu un tel problème du message initial ?), ni en quoi tes tests l’aurait démontré.
Oui, « ils n’apportent rien au problème posé. » Mais comme je l’ai déjà écrit : « outre le problème explicitement posé, j’ai noté qu’il y a un point à éclaircir » Et avant cela : « Il faut bien se rendre compte qu’il n’y a pas de souci d’encodage (cf. le commentaire “ j’ai aussi tenté avec signed char et unsigned char ” dans le code initial) »
Bon, « tu n’expliques pas en quoi cela serait un problème d’encodage » Comme marqué, « je crois comprendre que j’ai laissé comprendre » précise que ce que tu as cru comprendre n’est pas mon propos. J’invitais à approfondir les types de base et ne pas penser qu’il y a quelque lien avec le comportement constaté. Autrement dit, « // j'ai aussi tenté avec signed char et unsigned char » n’a pas de lien avec le problème exposé (et on pourrait même utiliser des types entiers pour ce code) ; mais je ne sais pas pourquoi tu tiens à me faire dire le contraire…
Quand à l’abus de langage, c’est lié à mon expérience et raccord avec la norme : j’ai travaillé trop longtemps-et-souvent sur des architectures où short est l’octet. (ceci trahi ma jeunesse.)
Hormis, les cas définis, le fait que le comportement par défaut soit laissé au compilateur fait que les deux cas peuvent exister ou qu’on puisse choisir : option /J chez MSVC et option -funsigned-char pour GCC qui était le comportement par défaut quand j’ai essayé Android NDK (j’avais lu que c’est pour des raisons de perf sur ARM…)
Connectez-vous pour pouvoir poster un message.
Connexion
Pas encore membre ?
Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte