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

Avatar
gl
Vincent Lefevre a écrit :
Pas vérifié, mais est-on sûr que (void *) 0 et (char *) 0 ont la même
représentation?



En C99 oui (je suppose donc que ça doit être également vrai en C90, mais
à vérifier). Je cite (§6.2.5 point 26):

"A pointer to void shall have the same representation and alignment
requirements as a pointer to a character type.39)"

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?).




Entre pointeurs sur des structures ca ne devrait pas poser de problème
par contre en pointeur sur structure et d'autre pointeur ce n'est
effectivement pas garantie

"All pointers to structure types shall have the same representation and
alignment requirements as each other. All pointers to union types shall
have the same representation and alignment requirements as each other.
Pointers to other types need not have the same representation or
alignment requirements."
Avatar
Vincent Lefevre
Dans l'article <g5oed0$2n3a$,
Marc Espie écrit:

Euh, les assert, c'est au runtime (en C). Donc beaucoup trop tard en prod.



Pas trop tard: tout d'abord, c'est reproductible, car ça se met
typiquement au début du programme, et ça se détecte lors du beta test
de la toute première version.

--
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 <487fbffd$0$909$,
gl écrit:

Vincent Lefevre a écrit :
> Pas vérifié, mais est-on sûr que (void *) 0 et (char *) 0 ont la même
> représentation?



En C99 oui (je suppose donc que ça doit être également vrai en C90, mais
à vérifier). Je cite (§6.2.5 point 26):



"A pointer to void shall have the same representation and alignment
requirements as a pointer to a character type.39)"



OK.

> 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?).



Entre pointeurs sur des structures ca ne devrait pas poser de problème
par contre en pointeur sur structure et d'autre pointeur ce n'est
effectivement pas garantie



Oui, le problème, c'est surtout entre (struct ... *) 0 et (void *) 0.
Intuitivement, on aurait tendance à penser qu'on peut écrire (void *) 0
à la place de n'importe quel pointeur nul, mais ce n'est pas vrai.

--
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
Vincent Lefevre a écrit :

Oui, le problème, c'est surtout entre (struct ... *) 0 et (void *) 0.
Intuitivement, on aurait tendance à penser qu'on peut écrire (void *) 0
à la place de n'importe quel pointeur nul, mais ce n'est pas vrai.




A priori le problème ne devrait pas se produire avec (void*)0, en effet:

"An integer constant expression with the value 0, or such an expression
cast to type void *, is called a null pointer constant.55) If a null
pointer constant is converted to a pointer type, the resulting pointer,
called a null pointer, is guaranteed to compare unequal to a pointer to
any object or function."

D'une manière plus générale (sauf utilisation vraiment très gore), il ne
devrait y avoir que peu de problème avec void* grâce aux conversions
implicites :

"A pointer to void may be converted to or from a pointer to any
incomplete or object type. A pointer to any incomplete or object type
may be converted to a pointer to void and back again; the result shall
compare equal to the original pointer."

En gros void* doit pouvoir "remplacer" n'importe quel T* (c'est
d'ailleurs nécessaire pour le fonctionnement de fonction du type de
memcpy()).
Le véritable problème se situe lorsqu'on mélange des pointeurs de
différents types (ce qui revient tout de même à mélanger torchon et
serviette et ne devrait donc même pas, dans un monde parfait, être
enviseagé) et non lors de l'utilisation de void*
Avatar
Vincent Lefevre
Dans l'article <487fc4c8$0$928$,
gl écrit:

Vincent Lefevre a écrit :
> Oui, le problème, c'est surtout entre (struct ... *) 0 et (void *) 0.
> Intuitivement, on aurait tendance à penser qu'on peut écrire (void *) 0
> à la place de n'importe quel pointeur nul, mais ce n'est pas vrai.



A priori le problème ne devrait pas se produire avec (void*)0, en effet:



"An integer constant expression with the value 0, or such an expression
cast to type void *, is called a null pointer constant.55) If a null
pointer constant is converted to a pointer type, the resulting pointer,
called a null pointer, is guaranteed to compare unequal to a pointer to
any object or function."



Cela parle juste de la comparaison de pointeurs, pas de la
représentation en mémoire, qui peut être importante dans le
cas des fonctions variadiques. Et d'ailleurs, il a bien été
dit que 0 et (char *) 0 n'étaient pas équivalents (avec des
cas concrets de problèmes dans ce cas particulier).

D'une manière plus générale (sauf utilisation vraiment très gore),
il ne devrait y avoir que peu de problème avec void* grâce aux
conversions implicites :



Il n'y a pas de conversions implicites pour les arguments des fonctions
variadiques.

--
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
Vincent Lefevre a écrit :
Cela parle juste de la comparaison de pointeurs, pas de la
représentation en mémoire, qui peut être importante dans le
cas des fonctions variadiques. Et d'ailleurs, il a bien été
dit que 0 et (char *) 0 n'étaient pas équivalents (avec des
cas concrets de problèmes dans ce cas particulier).



Oui, effectivement, en paramètre de fonction variadic, il peut y avoir
des problèmes.
Mais l'utilisation d'un pointeur (void*)0 en guide de sentinelle de fin
(le cas expliciter ci-dessus) ne devrait pas poser de problème (enfin
disons plutôt que je n'en vois pas pour l'instant) même si le pointeur
attendu n'est pas char* ou void* puisqu'il sera justement utiliser dans
une comparaison de type != NUMLL pour arrêter le traitement.

D'un autre côté, ayant plutôt tendance dans un tel cas de figure à
forcer le type correct comme suggéré ci-dessus (et à caster en void*
pour l'impression avec printf() et %p), il est vrai que je n'ai jamais
eu à creuser les problèmes potentiels de cette pratique.
Avatar
espie
In article <20080717220053$,
Vincent Lefevre <vincent+ wrote:
Dans l'article <g5oed0$2n3a$,
Marc Espie écrit:

Euh, les assert, c'est au runtime (en C). Donc beaucoup trop tard en prod.



Pas trop tard: tout d'abord, c'est reproductible, car ça se met
typiquement au début du programme, et ça se détecte lors du beta test
de la toute première version.



Bzzzt! Ca suppose que tu aies des tests de couverture *complets*, deja,
et qui tombent sur ces bugs-la. C'est quand la derniere fois que tu as
vu ce genre de choses ?

Dans tes tests, tu verifies ce qui se passe si tu alloues des structures
de 5G ? tu verifies que les tailles sont affichees correctement, par
exemple ?
Avatar
espie
In article <487fc4c8$0$928$, gl wrote:
Vincent Lefevre a écrit :

Oui, le problème, c'est surtout entre (struct ... *) 0 et (void *) 0.
Intuitivement, on aurait tendance à penser qu'on peut écrire (void *) 0
à la place de n'importe quel pointeur nul, mais ce n'est pas vrai.




A priori le problème ne devrait pas se produire avec (void*)0, en effet:

"An integer constant expression with the value 0, or such an expression
cast to type void *, is called a null pointer constant.55) If a null
pointer constant is converted to a pointer type, the resulting pointer,
called a null pointer, is guaranteed to compare unequal to a pointer to
any object or function."



Tu confonds *representation* et conversion implicite.

Vincent a souleve un probleme subtil: des pointeurs nuls de type distincts
n'ont pas forcement la meme representation en memoire. Si tu es dans un
contexte ou il n'y a pas de conversion implicite (ce qui est le cas sur
une ellipse), tu peux avoir un probleme.

Tres peu probable (je ne connais aucune implementation ou les pointeurs de
types de donnees ont des representations differentes, et je n'ai jamais
rencontre d'implementations ou les pointeurs de fonction sont differents,
meme si je sais que ca existe), mais possible d'apres la norme.
Avatar
candide
gl a écrit :

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 ?



Moi aussi ;)

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.



Oui, tu as raison, je crois que je vais abandonner le C pour du Python ;)
Avatar
gl
Marc Espie a écrit :
Tu confonds *representation* et conversion implicite.



Non, j'ai bien saisi la différence.
Par contre j'interprète peut être incorrectement ce passage de la norme.
Je comprends de ce passage que (void*)0 == NULL == (T*)NULL pour tout T
et ce indépendamment de la représentation de ces pointeurs.
N'est-ce pas le cas ?

Vincent a souleve un probleme subtil: des pointeurs nuls de type distincts
n'ont pas forcement la meme representation en memoire. Si tu es dans un
contexte ou il n'y a pas de conversion implicite (ce qui est le cas sur
une ellipse), tu peux avoir un probleme.



Je comprend bien le problème évoqué ici.
Mais je ne vois rien qui dans ce cas permet à une implémentation de
pouvoir considérer que (void*)0 n'est pas un pointeur nul et que la
comparaison entre(void*)0 et un quelconque pointeur pour déterminer sa
nullité ne soit pas possible.