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

Conversion des arguments de printf()

69 réponses
Avatar
candide
Ce post reprend Message-ID: <g5ift4$1ckv$1@biggoron.nerim.net>

Marc Espie a écrit :
> In article <487cb490$0$6783$426a34cc@news.free.fr>,
> candide <candide@free.invalid> wrote:
>> Je ne comprends pas ce que ça ajoute par rapport à
>>
>> printf("%u\n",strlen("toto"));
>>
>> puisque size_t est un type entier non signé.
>
> Et quelle taille ? printf est une fonction a nombre variable d'arguments,
> de prototype
> int printf(const char *, ...);
>
> il n'y a donc pas de verif de type passe le format. Si tu es sur une
plateforme
> ou size_t vaut unsigned long, ton printf ne marchera pas: il
recuperera juste
> un unsigned int comme parametre.

Bon j'ai cherché à me documenter sur la question, hélas rien de très
clair. Si je lis la FAQ de clc, il est effectivement explicitement dit
que je dois caster, je le saurai pour la prochaine fois.

Mais que se passe-t-il, étape par étape, quand l'instruction

printf("%u\n",strlen("toto"));


est exécutée ? La valeur de strlen("toto") est convertie dans le type
int parce que printf est variadique, c'est cela ?



_Ensuite_, cette valeur est convertie en unsigned int à cause du
spécificateur %u, c'est ça ?


Maintenant que se passe-t-il, étape par étape, quand l'instruction

printf("%u\n",(unsigned)strlen("toto"));

est exécutée ?

L'expression strlen("toto") est évaluée puis sa valeur est convertie en
unsigned int. Mais ensuite, pourquoi l'argument (unsigned)strlen("toto")
n'est-il pas converti en int puisque c'est un argument d'une fonction
variadique ?


Et puis, je ne vois pas où il est dit dans la norme que les arguments
entiers sont convertis en int. Si j'ai repéré l'endroit adéquat, la
conversion s'appelle "default argument promotion". Pour moi la
promotion, c'est la promotion :

char, short, champs de bit -> int ou unsigned int.




Mais la conversion

size_t (le type le plus large) -> int

c'est plus de la promotion, c'est une dégradation.

10 réponses

1 2 3 4 5
Avatar
Vincent Lefevre
Dans l'article <g5nfbk$2fca$,
Marc Espie écrit:

In article <20080717104138$,
Vincent Lefevre <vincent+ wrote:
>Mais la solution C90 risque de ne pas fonctionner avec certaines
>implémentations C99. Je suppose que tu fais cela avec un système
>style autoconf, ou alors que tu écris du code pour une ou plusieurs
>plateformes spécifiques.



Lesquelles ? As-tu deja rencontre des implementations suffisamment
tarees pour que size_t soit strictement plus grand que unsigned long ?



Ce n'est pas le cas sous MS-Windows (en 64 bits)?

C'est un changement qui a ete suffisamment decrie pour ne pas etre
suivi en pratique, et je crois qu'il a meme fait l'objet de plusieurs
defect reports...



Tu as des références? Quelle a été la réponse du comité?

--
Vincent Lefèvre - Web: <http://www.vinc17.org/>
100% accessible validated (X)HTML - Blog: <http://www.vinc17.org/blog/>
Work: CR INRIA - computer arithmetic / Arenaire project (LIP, ENS-Lyon)
Avatar
Antoine Leca
En news:g5nf8r$2fca$, Marc Espie va escriure:
In article <g5n3tk$7cn$,
Antoine Leca wrote:
En news:487efd1b$0$27507$, candide va escriure:
short d = 42;
printf("%h", d);

ton d sera promu en int lors de l'appel de printf.





OK, ça s'est la promotion des arguments. Une précision quand même :
que ce soit converti, parfait, maintenant quelle conséquence
*pratique* pour l'*utilisateur* de printf() ?



Aucune. En fait, le h dans
printf("%hd", d);
n'a pas d'utilité technique.



Faudrait verifier. Je ne suis pas sur que ton implementation de printf
n'ait pas le droit de supposer que d, cote intervalle de valeurs, est
un short (et donc faire n'importe quoi si tu lui passes une valeur
plus grande que SHRT_MAX).



Elle aurait certes le droit de renvoyer une valeur arbitraire ou de lever un
signal (mais doit le documenter, cf. 6.3.1.3p3) ; mais comme au départ de
l'action j'ai un short, il va être plus petit ou égal à SHRT_MAX (et plus
grand ou égal à SHRT_MIN)...


Ce qui a peut-être encore un peu d'utilité potentielle, ce serait une
formulation genre

type_entier_quelconque_de_rang_inférieur_ou_égal_à_int x;

printf("%hun", x);

car là on est sorti des cas d'indéfinition (quoique certains ergotent sur le
fait que passer un signed quand printf attend un unsigned soit indéfini, en
général je ne connais pas d'implémentation qui va buter là-dessus, cela
casserait trop de choses gratuitement); et le préfixe modificateur h va
forcer un masquage (qui est toujours bien défini) qui peut être intéressant.
On doit même pouvoir abuser de cela pour déterminer de manière bien tordue
la valeur de USHRT_MAX (avec x=-1), voire CHAR_BIT (via
snprintf(_,_,"%hho",UINT_MAX) et on compte les bits un peu comme au rami, le
'1' valant 1, le '3' valant 2 et le '7' valant 3) ; avec de bonnes chances
au passage d'avoir souvent des réponses erronées genre UINT_MAX...



Antoine
Avatar
espie
In article <g5nfi0$gc7$,
Antoine Leca wrote:
execl(nom_programme, args[1], args[2], args[3], (char*)0 );

pour indiquer que le pointeur nul à la fin (qui fait partie de la
spécification) est bien un char* comme attendu.



Formulation imparfaitement claire, ama. C'est surtout que, pour une fonction
variadique, un 0 tout seul, c'est meme pas un pointeur, c'est juste un entier.
Apres, tu pourrais caster en (void *)0 ou en (char *)0, ca suffira a faire
un pointeur nul.

(rappel: un 0 `sans cast' ne correspond pas a la meme chose selon le contexte.
Si contexte de pointeur, c'est un pointeur nul. Sans contexte, c'est un
entier. Et l'entier 0 n'est pas forcement representation valide du pointeur
nul...
Avatar
Antoine Leca
En news:20080717142612$, Vincent Lefevre va escriure:
Dans l'article <g5nfbk$2fca$,
Marc Espie écrit:

Mais la solution C90 risque de ne pas fonctionner avec certaines
implémentations C99. Je suppose que tu fais cela avec un système
style autoconf, ou alors que tu écris du code pour une ou plusieurs
plateformes spécifiques.





Lesquelles ? As-tu deja rencontre des implementations suffisamment
tarees pour que size_t soit strictement plus grand que unsigned long?



Ce n'est pas le cas sous MS-Windows (en 64 bits)?



Avec le compilo de Microsoft, si, mais à ma connaissance cette
implémentation n'est conforme ni à C90 ni à C99.

Par ailleurs, il me semble que Sun (?) avait un compilo vers 1997 qui avait
la même caractéristique (mais probablement pas en mode conforme, à supposer
qu'un tel mode existât).


C'est un changement qui a ete suffisamment decrie pour ne pas etre
suivi en pratique, et je crois qu'il a meme fait l'objet de plusieurs
defect reports...



Tu as des références? Quelle a été la réponse du comité?



Any changes along these lines would reduce consensus within the
Committee. The current state of this issue has been discussed
and reaffirmed on several occasions.


Réponse officiellement faite au commentaire #4 du Canada fait à l'occasion
de l'approbation du FCD en mars 99; le texte est typique de la position du
comité; pour mémoire, le texte du commentaire canadien était (en anglais, je
n'ai pas la VF sous la main, désolé) :

[ Extrait du document N2872 du sous-comité ISO/CEI JTC 1/SC22 ]

Comment #4
Category: Normative
Committee Draft Subsection: 6.2.5
Title:
Restrictions on long long
Description:
Proposal for a change to the Draft C standard (WG14/N843)
This proposal suggests a collection of small changes to the Draft C
Standard (WG14/N843) dated August 3, 1998. The changes are intended
to isolate long long int and implementation-defined extended integer
types from the common integer types. In particular, we wish to
ensure that size_t and ptrdiff_t must be one of the common integer
types, rather than long long or an implementation-defined extended
integer type. Also, we wish to ensure that no values are converted to
long long or an implementation-defined extended integer type,
except when the conversion is explicit. For example, on a system
where integers have 32 bits, a constant like 0xFFFFFFFF should be
converted to unsigned long rather than long long.

In order to implement this principle, we suggest the following
wording changes to various sections in the draft document.

6.2.5 Types
4. There are four standard signed integer types, designated as
signed char, short int, int, and long int. (These and other types
may be designated in several additional ways, as described in
6.7.2.) There is one standard extended signed integer
type designated as long long int. There may be additional
implementation-defined extended signed integer types. The standard
extended signed integer type and the implementation-defined
extended signed integer types are collected called the extended
signed integer types. The standard and extended signed integer
types are collectivel called signed integer types.

7.17 Common definitions <stddef.h>
<<Add to end of paragraph 2>>None of the above three types shall be
defined with an extended integer type, whether standard or
implementation-defined.


Si l'on fait abstraction de ce qui suit "Also" à propos des conversions vers
long long (qui est plus excessif), il me semble que c'est ce pour quoi Marc
(et beaucoup d'autres) veulent partir en guerre.



Antoine
Avatar
espie
In article <g5nqkm$q6s$,
Antoine Leca wrote:
Si l'on fait abstraction de ce qui suit "Also" à propos des conversions vers
long long (qui est plus excessif), il me semble que c'est ce pour quoi Marc
(et beaucoup d'autres) veulent partir en guerre.



Essentiellement, ca transforme du code portable pre-C99 en code non portable
en C99, qu'il faut reecrire en code explicitement C99 pour le rendre portable.

C'est d'autant plus chiant que le probleme est invisible... l'idiome
normal d'utilisation portable de printf() sur des size_t en C pre-C99 ne
genere aucun warning (puisqu'il y a un cast), il se contente juste d'etre
faux selon la norme.

Si c'est pas un gros probleme de compatibilite ascendante, je ne sais pas
ce que c'est...

En pratique, la plupart des gens raisonnables ignorent le probleme, et
considerent que les implementations ou
printf("%lu", (unsigned long)strlen(foo));
ne fonctionnent plus correctement ne meritent pas de vivre.

Le jour ou le comite a pondu cette verrue sur la norme, on ne sait pas
ce qu'ils avaient fume, mais le resultat est lamentable.
Avatar
Vincent Lefevre
Dans l'article <g5nqkm$q6s$,
Antoine Leca écrit:

En news:20080717142612$, Vincent Lefevre va escriure:
> Ce n'est pas le cas sous MS-Windows (en 64 bits)?



Avec le compilo de Microsoft, si, mais à ma connaissance cette
implémentation n'est conforme ni à C90 ni à C99.



De toute façon, les implémentations conformes, ça ne court pas les rues,
et dans la pratique il faut bien prendre en compte les implémentations
non conformes. Et la non conformité ne vient probablement pas du fait
que size_t est plus grand que long.

>> C'est un changement qui a ete suffisamment decrie pour ne pas etre
>> suivi en pratique, et je crois qu'il a meme fait l'objet de plusieurs
>> defect reports...
>
> Tu as des références? Quelle a été la réponse du comité?



Any changes along these lines would reduce consensus within the
Committee. The current state of this issue has been discussed
and reaffirmed on several occasions.



Ça n'apporte pas grand chose. Déjà que quand la norme impose quelque
chose, il peut être difficile d'imposer à une implémentation de la
suivre (cf le pragma FP_CONTRACT avec gcc et le bug 323 de gcc, alors
qu'il suffit juste de désactiver des optimisations pour corriger une
grosse partie du problème), alors quand cela reste conforme à la norme,
c'est encore plus compliqué.

Pour être sûr de garder la compatibilité avec de vieux codes pre-C99,
il aurait fallu définir un nouveau type long size_t. En effet, sur la
plupart des implémentations pre-C99, on avait long = 32 bits alors que
int pouvait être sur 16 ou 32 bits. Donc certains ont considéré que
long était synonyme de 32 bits (tout comme de nombreux codes supposent
actuellement que short est synonyme de 16 bits), et s'il avait fallu
garder les propriétés des anciennes implémentations, il aurait fallu
avoir long = 32 bits et size_t <= long, si bien que size_t = 32 bits
dans la pratique.

Il peut aussi se poser le problème du printf qui ne retourne qu'un int
au lieu d'un size_t (ou long): si int < size_t, on peut facilement se
retrouver avec un printf qui a un comportement indéfini sur certains
codes utilisateur.

--
Vincent Lefèvre - Web: <http://www.vinc17.org/>
100% accessible validated (X)HTML - Blog: <http://www.vinc17.org/blog/>
Work: CR INRIA - computer arithmetic / Arenaire project (LIP, ENS-Lyon)
Avatar
gl
candide a écrit :
Oui mais finalement ici, le degré de liberté est quasiment inexistant,
puisque size_t est le type de retour de sizeof et que sizeof d'un char
quel qu'il soit est 1, size_t sera un alias de unsigned char ou char (si
les caractères sont non signés) ou à la rigueur d'une version qualifiée
de ces derniers, non ?




J'ai beaucoup de mal a comprendre ce qui te fait conclure ici que size_t
sera forcément un alias de unsigned char ou char ?
Le fait que sizeof(char) = 1 n'implique en rien le type "réel" de size_t
qui par ailleurs est plus généralement un alias de unsigned int ou
unsigned long.
Avatar
Vincent Lefevre
Dans l'article <g5nvjj$2m68$,
Marc Espie écrit:

C'est d'autant plus chiant que le probleme est invisible... l'idiome
normal d'utilisation portable de printf() sur des size_t en C pre-C99 ne
genere aucun warning (puisqu'il y a un cast), il se contente juste d'etre
faux selon la norme.



Cependant on ne devrait pas se baser sur les warnings (complètement
optionnels) pour vérifier des propriétés de ce genre. Un assert
suffit généralement dans la pratique pour *détecter* des problèmes
éventuels. Et grâce aux optimisations, cela ne doit pas générer de
code supplémentaire quand tout fonctionne bien.

--
Vincent Lefèvre - Web: <http://www.vinc17.org/>
100% accessible validated (X)HTML - Blog: <http://www.vinc17.org/blog/>
Work: CR INRIA - computer arithmetic / Arenaire project (LIP, ENS-Lyon)
Avatar
Vincent Lefevre
Dans l'article <g5np2s$2lfk$,
Marc Espie écrit:

Formulation imparfaitement claire, ama. C'est surtout que, pour une
fonction variadique, un 0 tout seul, c'est meme pas un pointeur,
c'est juste un entier. Apres, tu pourrais caster en (void *)0 ou en
(char *)0, ca suffira a faire un pointeur nul.



Pas vérifié, mais est-on sûr que (void *) 0 et (char *) 0 ont la même
représentation? En tout cas, il faut faire gaffe quand on a des pointeurs
sur des structures, où là ce n'est pas forcément le cas (dans la pratique,
je n'ai jamais vu de problème, mais qui dit que cela ne pourrait pas en
poser dans le futur, avec des compilateurs de plus en plus complexes, soit
pour optimiser, soit pour détecter des failles éventuelles?).

--
Vincent Lefèvre - Web: <http://www.vinc17.org/>
100% accessible validated (X)HTML - Blog: <http://www.vinc17.org/blog/>
Work: CR INRIA - computer arithmetic / Arenaire project (LIP, ENS-Lyon)
Avatar
espie
In article <20080717213141$,
Vincent Lefevre <vincent+ wrote:
Dans l'article <g5nvjj$2m68$,
Marc Espie écrit:

C'est d'autant plus chiant que le probleme est invisible... l'idiome
normal d'utilisation portable de printf() sur des size_t en C pre-C99 ne
genere aucun warning (puisqu'il y a un cast), il se contente juste d'etre
faux selon la norme.



Cependant on ne devrait pas se baser sur les warnings (complètement
optionnels) pour vérifier des propriétés de ce genre. Un assert
suffit généralement dans la pratique pour *détecter* des problèmes
éventuels. Et grâce aux optimisations, cela ne doit pas générer de
code supplémentaire quand tout fonctionne bien.



Euh, les assert, c'est au runtime (en C). Donc beaucoup trop tard en prod.
1 2 3 4 5