Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

printf() [amies drosophiles, serrez les miches !]

7 réponses
Avatar
Pierre Maurette
Bonjour,

Je veux imprimer un uint8_t. Naturellement, j'utilise un PRIu8 dans la
chaîne de format. Je compile en allumant les warnings et remarques sous
iCC 10.0, qui semble trouver une incompatibilté entre mon argument et
la chaîne de format. Je vérifie, mon PRIu8 est un "u", je comprends mal
la remarque, pensant que le uint8_t était promu en unsigned. Je mets un
%hhu, ou un %hh"PRIu8", plus de remarque.
Le %hh"PRIu8" est idiot, autant se contenter de %hhu et ne pas utiliser
inttypes.h dans ce cas. D'autant qu'en changeant d'implémentation, je
me verrais bien me retrouver avec un %hh"PRIu8" transformé en %hhhhu.

Au final, je me contente d'un %u en castant l'argument en unsigned. Et
je remarque je j'en arrive souvent à cette solution pour:
- imprimer des enum.
- imprimer des types typedef-és dans une bibliothèque qui ne fournit
pas la chaîne de format.
- imprimer des size_t sans warning avec mon "gcc version 3.4.5 (mingw
special)" sous Windows.

Bon week-end...

--
Pierre Maurette

7 réponses

Avatar
Stéphane Goujet
Le Fri, 06 Jul 2007 08:21:43 +0200
Pierre Maurette a écrit:

Je veux imprimer un uint8_t. Naturellement, j'utilise un PRIu8 dans
la chaîne de format. Je compile en allumant les warnings et remarques
sous iCC 10.0, qui semble trouver une incompatibilté entre mon
argument et la chaîne de format. Je vérifie, mon PRIu8 est un "u", je
comprends mal la remarque, pensant que le uint8_t était promu en
unsigned. Je mets un %hhu, ou un %hh"PRIu8", plus de remarque.
Le %hh"PRIu8" est idiot, autant se contenter de %hhu et ne pas
utiliser inttypes.h dans ce cas. D'autant qu'en changeant
d'implémentation, je me verrais bien me retrouver avec un %hh"PRIu8"
transformé en %hhhhu.


Vous pourriez regarder la sortie du préprocesseur, pour savoir quelle
est la chaîne de format après le remplacement de PRIu8.

--
Stéphane Goujet.

Avatar
Antoine Leca
En news:, Pierre Maurette va escriure:
Je veux imprimer un uint8_t.


printf("%d", le_uint8);


Naturellement, j'utilise un PRIu8 dans la chaîne de format.


Non. « Naturellement, j'ai essayé d'utiliser un PRIu8 dans la chaîne de
format. Le résultat est incorrect. En conséquence, j'ai perdu un temps
précieux pour essayer de me compliquer la vie, et j'ai seulement trouvé un
bogue dans mon implémentation. » serait une description plus correcte...

Je compile en allumant les warnings et remarques
sous iCC 10.0, qui semble trouver une incompatibilté entre mon
argument et la chaîne de format.


Ah ah... A priori, activer cet avertissement est quelque chose que le
développeur de ton <inttypes.h> a omis de faire...

Je vérifie, mon PRIu8 est un "u",


Oups !

je comprends mal la remarque,


Pourquoi ? elle est pourtant limpide...

pensant que le uint8_t était promu en unsigned.


[Réponse à la question ci-dessus]
Eh non ! Un uint8_t (unsigned char, normalement) sera promu en int, ce
dernier type préservant toutes les valeurs possible du type initial (de 0 à
255, ce n'est pas difficile).

Une promotion en unsigned ne respecterait pas la norme C90, et c'est
parfaitement incompatible avec l'existence de PRIu8.
Si tu as un compilo qui a une option permettant de forcer "preserve
unsignedness", il faut absolument la désactivée sur du code d'aujourd'hui,
c'est seulement prévu pour les reliques.


Je mets un %hhu, ou un %hh"PRIu8", plus de remarque.


Bouh, c'est horrible.
De plus, imagine que tu prennes une implémentation correcte, où PRIu8 vaut
"d" ou bien vaut "hhu", et tu vas récupérer comme chaîne de format "hhd"
(qui va rajouter un signe là où il n'y en a pas au départ!) ou "hhhhu"
(boum).


Antoine

Avatar
Antoine Leca
En news:,
Stéphane Goujet écrivit:
Le Fri, 06 Jul 2007 08:21:43 +0200
Pierre Maurette a écrit:

[couic]

argument et la chaîne de format. Je vérifie, mon PRIu8 est un "u", je
[couic]



Vous pourriez regarder la sortie du préprocesseur, pour savoir
quelle est la chaîne de format après le remplacement de PRIu8.


Si ce n'est pas "u", il faut que Pierre change ou bien de lunettes, ou bien
de compilateur, ou à tout le moins qu'il passe un bon détecteur de rootkit
sur sa bécane...


Antoine


Avatar
Pierre Maurette
En news:, Pierre Maurette va escriure:
Je veux imprimer un uint8_t.


printf("%d", le_uint8);


Naturellement, j'utilise un PRIu8 dans la chaîne de format.


Non. « Naturellement, j'ai essayé d'utiliser un PRIu8 dans la chaîne de
format. Le résultat est incorrect. En conséquence, j'ai perdu un temps
précieux pour essayer de me compliquer la vie, et j'ai seulement trouvé un
bogue dans mon implémentation. » serait une description plus correcte...


Le "naturellement" était un peu littéraire. Je voulais dire que ça
fonctionnait bien et que j'ai pensé qu'il était "naturel" d'utiliser
PRIu8. Comme il est naturel d'indicer par des size_t même su unsigned
fonctionne. J'ai pensé...


Je compile en allumant les warnings et remarques
sous iCC 10.0, qui semble trouver une incompatibilté entre mon
argument et la chaîne de format.


Ah ah... A priori, activer cet avertissement est quelque chose que le
développeur de ton <inttypes.h> a omis de faire...

Je vérifie, mon PRIu8 est un "u",


Oups !

je comprends mal la remarque,


Pourquoi ? elle est pourtant limpide...

pensant que le uint8_t était promu en unsigned.


[Réponse à la question ci-dessus]
Eh non ! Un uint8_t (unsigned char, normalement) sera promu en int, ce
dernier type préservant toutes les valeurs possible du type initial (de 0 à
255, ce n'est pas difficile).

Une promotion en unsigned ne respecterait pas la norme C90, et c'est
parfaitement incompatible avec l'existence de PRIu8.
Si tu as un compilo qui a une option permettant de forcer "preserve
unsignedness", il faut absolument la désactivée sur du code d'aujourd'hui,
c'est seulement prévu pour les reliques.


Ce n'est pas du temps perdu, de mon point de vue du moins. Pas plus que
celui passé à concocter une réponse. Une paire d'heures pour passer du
confus au relativement clair, c'est d'un rendement exceptionnel ;-)

Reprenons donc. J'utilisais %d. Je me suis dit que si PRIu8 existait,
c'est qu'il devait être "mieux" de l'utiliser. C'est là, en passant sur
iCC, que j'ai vu ce warning (que je n'avais pas avec gcc). Pas certain
de comprendre la norme, j'ai Googlé et malheureusement zappé une phrase
alors que le site que je consultais
<URL:http://www-clips.imag.fr/commun/bernard.cassagne/Introduction_ANSI_C/node98.html&gt;
était exempt d'erreur en tout cas sur ce sujet. J'ai admis - facilement
- l'idée que j'avais pu me tromper et qu'un unsigned char était
converti en unsigned. PRIu8 ou "u", peu importe, je sentais bien une
incohérence. Mais je remets en cause mes compétences, c'est à dire
j'admets le fait que je ne comprends pas quelque chose, bien avant de
soupçonner mon inttypes.h.
Pour info, c'est celui installé je suppose avec le paquet (Debian /
dépôt Ubuntu) libc6-dev, en version 2.5.0, et que iCC utilise. C'est
sans doute à ce niveau que se situe l'explication.

En fait, je rencontre de vrais problèmes de chaînes de format -
contrairement à celui-ci qui n'en est pas un - pour compiler sur
plusieurs plateformes. Bon, beaucoup viennent d'un vieux mingw que j'ai
sous Windows. Et là caster l'argument du printf() est une solution qui
m'évite trop de #ifdef. Quand je connais l'ordre de grandeur, un %u,
%lu, %llu et un cast de mon size_t pare la non reconnaissance du %zu
dans mingw.
Quand je n'ai pas besoin de toutes les décimales à l'affichage
(toujours), un cast en double à l'affichage me simplifie en partie le
code (et là, ce n'est pas uniquement un problème de warning). Le fait
que le souci vienne de la RTL Microsoft ne change rien au fait que le
problème existe et qu'il est chiant.

En tout cas merci pour la réponse claire sur la promotion.

Je mets un %hhu, ou un %hh"PRIu8", plus de remarque.


Bouh, c'est horrible.
De plus, imagine que tu prennes une implémentation correcte, où PRIu8 vaut
"d" ou bien vaut "hhu", et tu vas récupérer comme chaîne de format "hhd"
(qui va rajouter un signe là où il n'y en a pas au départ!) ou "hhhhu"
(boum).


Oui, c'est horrible. Il ne m'est jamais venu à l'esprit d'utiliser
%hh"PRIu8", quand même. Et je signale moi-même le danger de se
retrouver avec %hhhhu. De toute façon, l'idée derrière les PRIxxxx est
de les utiliser sans en connaître le contenu, et les amender d'un h,
hh, z, etc. n'a aucun sens.

--
Pierre Maurette


Avatar
Antoine Leca
En news:,
Pierre Maurette va escriure:

En fait, je rencontre de vrais problèmes de chaînes de format -
contrairement à celui-ci qui n'en est pas un - pour compiler sur
plusieurs plateformes. Bon, beaucoup viennent d'un vieux mingw que
j'ai sous Windows.


Pas exactement. En fait, les problèmes (genre %zu, ou %lld) que tu
rencontres avec ton Mingw ne viennent pas du compilateur, mais de la
bibliothèque standard utilisée, qui se trouve être MSVCRT.DLL, autrement dit
celle du compilateur Visual C++ v.2, un truc genre '94 vintage... qui
n'offre pas la compatibilité C99, évidement.

Je ne sais pas si les versions plus récentes de Mingw résolvent cela. Déjà,
je soupçonne qu'ils sont passé à GCC v4 (une autre classe de souci en
perspective). Mais surtout, il n'y a pas de « bonne » solution, ou plutôt
cela dépend des cas.

En gros, il y a deux solutions « classiques », l'une est de fournir une DLL
ou une bibliothèque statique complémentaire qui s'insère avant MSVCRT et
donne le support C99 pour les choses qui manquent (y compris _doprintf);
problème, soit les exécutables deviennent tous sensiblement plus gros
(printf() est l'argument nº1 pour avoir inventer les bibliothèqes dynamiques
au départ), ou alors il faut installer une nouvelle DLL de plus,
probablement dans %SystemDir%, et bienvenue dans l'enfer des DLLs...

L'autre solution est de changer de DLL de référence, par exemple prendre
MSVCRT80 (fournie avec le compilo VS2005); mais cette DLL n'est pas présente
dans toutes les machines, donc il faut envoyer les utilisateurs se connecter
sur le site de Microsoft pour la récupérer, etc. On avait le même souci en
1997 au début de Mingw, pour que cela marche avec WinNT 3.? (qui n'avait
/que/ crtdll.dll) il était nécessaire d'aller récupérer msvcrt.dll, et cela
n'était pas une partie de plaisir...


Antoine

Avatar
PIGUET Bruno
Pierre Maurette wrote:
Bonjour,

Je veux imprimer un uint8_t. Naturellement, j'utilise un PRIu8 dans la
chaîne de format.


Auriez-vous une référence de documentation complète et claire sur ce
sujet (les [u]int[nn]_t et les formats PRI*) associés ?
Les docs classiques (K&R, Dax, Braquelaire) n'en parlent pas.
Une approche empirique : man inttypes.h sur certains systèmes (pas
sur gnu-linux/gcc), Google, et la lecture directe de inttypes.h,
permettent de se débrouiller, mais j'apprécierais d'avoir une vue plus
large : origine, disponibilité, portabilité, alternatives, ...

Merci,

Bruno.

Avatar
Antoine Leca
En news:f870rh$a8m$, Bruno PIGUET va escriure:
Auriez-vous une référence de documentation complète et claire sur
ce sujet (les [u]int[nn]_t et les formats PRI*) associés ?
Les docs classiques (K&R, Dax, Braquelaire) n'en parlent pas.


Logique, ces ouvrages couvrent la norme C90, et il s'agit d'extensions
introduites par X/Open v5 (1998) ou C99.

Une approche empirique : man inttypes.h sur certains systèmes (pas
sur gnu-linux/gcc), Google, et la lecture directe de inttypes.h,
permettent de se débrouiller, mais j'apprécierais d'avoir une vue plus
large : origine, disponibilité, portabilité, alternatives, ...


Humm, la question est large (d'aucun écriraient imprécise).

Origine : Je distinguerais deux étapes.
En ce qui concerne les [u]int{NN}_t et <inttypes.h>, ceux-ci viennent du
monde *nix. La filiation est longue, on trouve des références datant de 1992
(travaux de SGI pour faciliter la portabilité vers le monde 64 bits ; un
important personnage est John R. Mashey); je crois qu'ils étaient définis
par la norme X/Open 4.2 (« Unix95 »), en tous cas ils le sont dans la
version 5 (alias Unix98, alias SUS v2) ; le contenu est restreint à
[u]int{NN}_t (soit bien moins que l'actuel <stdint.h>).
En ce qui concerne les int_least*, PRI* et autres SCN*, [u]int_max_t et tout
ce qui va avec, et aussi de <stdint.h>, il s'agit d'une /invention/ de la
norme C99 (issue d'une proposition de John W. Kwan, _/Extended Integers For
C/_, datée 8/6/1995 dans mes archives ; et des propositions de Frank
Farance, qui furent suivies d'une looooongue discussion sur le thème "long
long".)
Documentation sur tout ceci en français : bah, pas grand chose à vrai
dire...
En anglais par contre, il y a abondance, y compris les documents datés
1995-1998 qui racontent par le menu la genèse de la norme C99, disponible
via Google.

Définition (question non posée explicitement, mais cela peut aider):
En français je n'ai pas de référence.
Le texte précis en anglais de la norme C99 est accessible gratuitement sur
Internet (sa référence est N1124); celui des normes SUS (v2 et v3) est sur
http://www.unix.org/ (et www.opengroup.org); pour ce qui est de <inttypes.h>
strictement, on peut utilement se reporter à
http://www.dinkumware.com/manuals/default.aspx?manual=compleat&page=inttypes.html
(qui décrit une implémentation définie, mais les explications sont
transposables.)


Alternatives :
Rappel liminaire : [u]int{NN}_t est un nom réservé par les normes
Posix et dérivés, donc il est en principe interdit de déclarer
soi-même un identificateur de ce nom. De même, les entêtes
(entre <...>) doivent être fourni par l'implémentation, le fait
de rajouter « à la main » un fichier inttypes.h à un endroit
/stratégique/ n'est qu'un pis-aller qui pose plein de problème
à moyen terme (genre lors d'une actualisation du compilateur...)
Les déclarations équivalentes à [u]int{NN}_t peuvent être facilement
déduites, pour les cas les plus communs (multiplets de 8 bits en complément
à 2), des données de <limits.h> ; avec un peu plus de circonvolution, on
peut aussi déclarer les PRI* correspondants.
Certaines implémentations n'ont pas de [u]int[nn]_t, mais déclarent
{u|i}{NN}_t, ou [u]int{NN} (sans le _t), logées dans des entêtes diverses
mais <sys/types.h> est un bon pari en première instance.
Une autre approche est suivie dans http://www.lysator.liu.se/c/q8/index.html


Disponibilité : Cela dérive de la discussion sur les origines.
Elle dépend du niveau de fonctionnalité recherchée : [u]int{NN}_t est
(relativement) courant ; PRI*, [u]intmax_t ou strtoimax(), moins. Il est
facile de savoir si les extensions C99 sont disponibles une fois que
<inttypes.h> est #inclus, elles sont prévues pour être repérées grâce à la
construction
#ifdef PRIiMAX
/*... Oooooui ...*/
#endif
Le problème est comment peut-on deviner que l'entête est disponible... et
là, je n'ai pas de solution à toute épreuve.
Si on protège par #if __STDC_VERSION__-0 >= 199901, ou #if _XOPEN_VERSION-0
= 500, on est tranquille. #Inclure <inttypes.h> et utiliser [u]int{NN}_t a
de bonnes chances de marcher sur une plateforme *nix. Sinon, cela dépend, en

particulier de l'implémentation de la bibliothèque standard (indépendament
du compilateur, donc) par rapport à la conformité à C99, au moins sur ce
point (autrement dit, il n'est pas indispensable que soit implémenté les
complexes, les UCNs */et/* l'arithmétique IEC 60559.)


Portabilité :
La révision de la norme C++ (C++0x) incorporera cet entête et <cinttypes.h>,
et la plupart des implémentations qui visent cette norme le proposent d'ores
et déjà, même si elle ne visent pas le support complet de C99 (donc ne
définient pas __STDC_VERSION__).
En dehors de cela, c'est un peu le désert au niveau garantie de portabilité
: SUS est évidemment restreint à *nix ; C99 n'est pas considéré (en dehors
du monde *nix) comme une norme aussi décisive que le fut C90 en son temps,
et beaucoup d'utilisation de C n'évoluent plus, se calcifient comme ce fut
le cas dans le passé pour Cobol et dans une moindre mesure Fortran, ou comme
c'est le cas pour SQL aujourd'hui...

Sinon, il faut garder à l'esprit que l'existence de int64_t ne garantit pas
automatiquement celle de uint8_t (et toutes les permutations sont possibles)
; par exemple, un compilateur qui manipule seulement des objets de 64 bits
(cela existe) ne pourra pas déclarer int8_t... De même, une machine où les
seuls types de données accessibles sont de 8 ou 32 bits ne définira pas
int16_t. D'autres machines ont des « trous » pour gérer certains types
signés ou non signés (genre uint16_t existe ainsi que plein de choses liées
à Unicode, mais pas de int16_t car la machine ne sait pas faire de
multiplication signées 16×16 bits ; ou int32_t existe sur une architecture
64 bits, mais pas de uint32_t qui obligeraient à faire des tas de
masquages).


Pour les questions couvertes par ..., j'ai besoin d'un peu plus de
précision.


Antoine