Mis à part %hn
et %hhn
(où h
ou hh
spécifie la taille de l'objet pointé vers ), quel est le point des modificateurs h
et hh
pour les spécificateurs de format printf
?
En raison des promotions par défaut requises par la norme à appliquer pour les fonctions variadiques, il est impossible de passer des arguments de type char
ou short
(ou de leurs variantes signées/non signées) à printf
.
Selon 7.19.6.1 (7), le modificateur h
:
Spécifie qu'une spécification de conversion d, i, o, u, x ou X suivante s'applique à un short int ou unsigned short int (l'argument aura été promu en fonction des promotions d'entiers, mais sa valeur devra être convertie en short int ou unsigned short int avant l'impression); ou qu'un spécificateur de conversion n suivant s'applique à un pointeur sur un court argument int.
Si l'argument était en fait de type short
ou unsigned short
, alors la promotion à int
suivie d'une conversion en retour à short
ou unsigned short
produira la même valeur que la promotion à int
sans conversion. Ainsi, pour les arguments de type short
ou unsigned short
, %d
, %u
, etc. devrait donner des résultats identiques à %hd
, %hu
, etc. (de même que pour les types char
et hh
).
Autant que je sache, la seule situation où le modificateur h
ou hh
pourrait éventuellement être utile est lorsque l'argument lui a passé une variable int
en dehors de la plage short
ou unsigned short
, par exemple.
printf("%hu", 0x10000);
mais si je comprends bien, le fait de passer un type incorrect comme celui-ci entraîne un comportement indéfini, de sorte que vous ne pouvez pas vous attendre à ce qu'il imprime 0.
Un cas réel que j'ai vu est un code comme celui-ci:
char c = 0xf0;
printf("%hhx", c);
où l'auteur s'attend à ce qu'il imprime f0
malgré l'implémentation ayant un type char
simple qui est signé (auquel cas, printf("%x", c)
afficherait fffffff0
ou similaire). Mais cette attente est-elle justifiée?
(Remarque: ce qui se passe, c'est que le type d'origine était char
, qui est promu à int
et reconverti en unsigned char
au lieu de char
, modifiant ainsi la valeur imprimée. Mais la norme spécifie-t-elle ce comportement ou s'agit-il d'un détail d'implémentation? ce logiciel cassé pourrait compter?)
Une raison possible: pour la symétrie avec l'utilisation de ces modificateurs dans les fonctions d'entrée formatées? Je sais que cela ne serait pas strictement nécessaire, mais peut-être y at-il eu un bon rapport qualité-prix?
Bien qu'ils ne mentionnent pas l'importance de la symétrie pour les modificateurs "h" et "hh" dans le document de justification C99 , le comité le mentionne comme une considération pour expliquer pourquoi le spécificateur de conversion "% p" est pris en charge. fscanf()
(même si cela n'était pas nouveau pour C99 - la prise en charge "% p" est en C90):
La conversion du pointeur d'entrée avec% p a été ajoutée à C89, bien que ce soit évidemment risqué, pour une symétrie avec fprintf.
Dans la section sur fprintf()
, le document de justification C99 explique que "hh" a été ajouté, mais renvoie simplement le lecteur à la section fscanf()
:
Les modificateurs de longueur% hh et% ll ont été ajoutés dans C99 (voir le §7.19.6.2).
Je sais que c'est un fil ténu, mais je spécule quand même, alors je me suis dit que je donnerais n'importe quel argument.
En outre, par souci de complétude, le modificateur "h" figurait dans la norme C89 initiale. Il serait probablement présent même si cela n’était pas strictement nécessaire en raison d’une utilisation largement répandue, même si l’exigence technique n’avait peut-être pas été utilisée. .
La seule utilisation à laquelle je peux penser est de passer un unsigned short
ou unsigned char
et d'utiliser le spécificateur de conversion %x
. Vous ne pouvez pas simplement utiliser un %x
nu - la valeur peut être promue à int
au lieu de unsigned int
et vous avez alors un comportement non défini.
Vos alternatives sont soit de convertir explicitement l'argument en unsigned
; ou d'utiliser %hx
/%hhx
avec un argument nu.
En mode %...x
, toutes les valeurs sont interprétées comme non signées. Les nombres négatifs sont donc imprimés en tant que conversions non signées. Dans l'arithmétique du complément à 2, utilisée par la plupart des processeurs, il n'y a pas de différence de motif binaire entre un nombre négatif signé et son équivalent non signé positif, défini par l'arithmétique de module (additionnant la valeur maximale du champ plus un au nombre négatif, selon à la norme C99). De nombreux logiciels, en particulier le code de débogage le plus susceptible d'utiliser %x
-, supposent de manière muette que la représentation en bits d'une valeur négative signée et de sa distribution non signée est la même, ce qui n'est vrai que sur la machine du complément à 2.
La mécanique de cette distribution est telle que les représentations hexadécimales de la valeur impliquent toujours, voire inexactement, qu'un nombre a été rendu dans le complément à 2, tant qu'il n'a pas atteint la condition Edge où les différentes représentations entières ont des plages différentes. Ceci est même vrai pour les représentations arithmétiques où la valeur 0 n'est pas représentée avec le modèle binaire de tous les 0.
Un short
négatif affiché sous la forme d'un unsigned long
en hexidécimal sera donc, sur n'importe quel ordinateur, complété avec f
, en raison d'une extension de signe implicite dans la promotion, qui printf
imprimera . La valeur est la même chose, mais elle est vraiment trompeuse sur le plan visuel quant à la taille du champ, impliquant une quantité significative de plage qui n'est tout simplement pas présente.
%hx
tronque la représentation affichée pour éviter ce remplissage, exactement comme vous l'avez conclu de votre cas d'utilisation réel.
Le comportement de printf
est indéfini lorsqu'il est passé un int
en dehors de la plage de short
qui doit être imprimé en tant que short
, mais l'implémentation la plus simple éloigne tout simplement le bit haut. par un downcast brut, donc si la spécification ne requiert aucun comportement spécifique, à peu près toute implémentation rationnelle va simplement effectuer la troncature. Il existe généralement de meilleures façons de le faire.
Si printf ne remplit pas les valeurs ni affiche des représentations non signées des valeurs signées, %h
n'est pas très utile.
Les arguments variadic à printf()
et autres sont automatiquement promus à l'aide des conversions par défaut. Ainsi, toutes les valeurs short
ou char
sont promues à int
lorsqu'elles sont transmises à la fonction.
En l'absence des modificateurs h
ou hh
, vous devez masquer les valeurs transmises pour obtenir le comportement correct de manière fiable. Avec les modificateurs, il n’est plus nécessaire de masquer les valeurs; L'implémentation printf()
fait le travail correctement.
Spécifiquement, pour le format %hx
, le code à l'intérieur de printf()
peut faire quelque chose comme:
va_list args;
va_start(args, format);
...
int i = va_arg(args, int);
unsigned short s = (unsigned short)i;
...print s correctly, as 4 hex digits maximum
...even on a machine with 64-bit `int`!
Je suppose allègrement que short
est une quantité de 16 bits; la norme ne garantit pas cela, bien sûr.
J'ai trouvé utile d'éviter de lancer lors du formatage de caractères non signés au format hexadécimal:
sprintf_s(tmpBuf, 3, "%2.2hhx", *(CEKey + i));
C'est une commodité mineure de codage et une apparence plus nette que de multiples distributions (IMO).
la vérification de la taille de snprintf est un autre endroit pratique.
char arr[4];
char x='r';
snprintf(arr,sizeof(arr),"%d",r);
il vous oblige donc à utiliser un plus grand caractère lorsque vous utilisez% d lors du formatage d'un caractère
voici un commit qui montre ces correctifs au lieu d’augmenter la taille du tableau de caractères lorsqu’ils ont changé% d en% h. cela donne aussi une description plus précise
Je suis d'accord avec vous pour dire que ce n'est pas strictement nécessaire et que, pour cette raison, cela ne sert à rien dans une fonction de bibliothèque C :)
Cela pourrait être "sympa" pour la symétrie des différents drapeaux, mais c'est surtout contre-productif car cela cache la règle "conversion en int
".