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
espie
In article <487d095d$0$18576$,
candide wrote:

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

printf("%un",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 ?



Non, elle est plus ou moins laissee telle quelle, en particulier en terme
de taille (sinon, tu fais comment pour passer des long a des fonctions
variadiques, gros malin ?)

... pour une fois, c'est juste du bon sens. Si tu veux le texte
de la norme, c'est 6.5.2.2 (qui explique les default argument promotions en
termes d'integer promotions et de float -> double et specifient que celles-ci
s'appliquent pour l'ellipse), et 6.3.1.1 (qui expliquent les integer
promotions, qui grosso modo reviennent a convertir tout ce qui est `plus petit'
en int ou unsigned int, et a laisser intact tout ce qui est plus grand).

Bonne exegete. ;-)
Avatar
espie
In article ,
Erwan David wrote:
(Marc Espie) écrivait :



In article <487d095d$0$18576$,
candide wrote:





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







printf("%un",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 ?







Non, elle est plus ou moins laissee telle quelle, en particulier en terme
de taille (sinon, tu fais comment pour passer des long a des fonctions
variadiques, gros malin ?)





... pour une fois, c'est juste du bon sens. Si tu veux le texte
de la norme, c'est 6.5.2.2 (qui explique les default argument promotions en
termes d'integer promotions et de float -> double et specifient que celles-ci
s'appliquent pour l'ellipse), et 6.3.1.1 (qui expliquent les integer
promotions, qui grosso modo reviennent a convertir tout ce qui est


`plus petit'
en int ou unsigned int, et a laisser intact tout ce qui est plus grand).





Mais il faut se méfier en passant d'une architecture où les int font 32
bits à une où ils font 16 bits. On peut avoir des surprises.



Je ne comprend pas le sens de ta remarque dans ce contexte.

On est en train de parler de portabilite depuis quelques plombes,
les archis archaiques ou les int font 16 bits font partie des problemes
de portabilite.

Normalement, les memes paragraphes s'appliquent, sauf si bien sur tu n'as
pas de compilo respectant la norme...
Avatar
Antoine Leca
En news:487d095d$0$18576$, candide va escriure:
Mais que se passe-t-il, étape par étape, quand l'instruction

printf("%un",strlen("toto"));

est exécutée ?



Il y a deux temps bien distincts.


Premier temps, la compilation.
Je suppose qu'auparavant, tu as mis les #include appropriés, ou à tout le
moins que les prototypes (de printf _et_ de strlen) sont correctement
déclarés.

D'abord, le compilateur évalue "toto", c'est un objet (d'où allocation) et
on lui colle son type, en l'occurence char[5] (ou const char[5], je ne
rentre pas dans ce détail-là).

Ensuite il évalue l'appel à la fonction strlen(); le nom strlen est évalué,
le compilateur remarque qu'il s'agit d'une fonction avec comme type
size_t(const char*); puis l'opération appel de fonction est évaluée, avec
comme opérandes d'un côté une fonction de l'autre un tableau: comme ni l'un
ni l'autre ne convienne aux règles de C, le compilateur réarrange les deux,
la fonction est convertie en pointeur vers elle, de type size_t(*)(const
char*); et l'argument de type char[5] est converti en char*, c'est le
classique passage de tableau; ensuite le compilateur passe aux règles de
conversion pour l'appel, et remarque qu'un char* peut être passé à une
fonction qui attend un const char*, donc pas de problème; le compilateur
peut donc maintenant (enfin) passer à la génération du code d'appel de
strlen; ici, pas mal de compilateur vont remarquer que strlen est une
fonction particulière ("built-in") et générer un code spécial, par exemple
expansion en ligne ("inline"), ou même remplacement par la valeur 4;
d'autres vont empiler, instruction d'appel de sous-programme, et dépiler;
les détails sont sans importance.
Enfin, le compilateur attribue le type size_t au résultat, et on peut passer
à la suite.

La suite, c'est "%un", je ne rentre pas dans les détails, idem "toto" sauf
pour le type char[4].

On arrive à l'appel de la fonction printf(,). Le nom printf est évalué, une
fonction de type int(const char*, ...). Ensuite l'opération d'appel de
fonction _variadique_: on a d'abord les mêmes transformations que ci-dessus,
d'un côté on a maintenant un int(*)(const char*, ...), et de l'autre le
tableau char[3] est converti en char*, qui est OK pour le paramètre attendu
comme const char*; pour l'expression, on applique les règles de promotions:
ainsi si le type (entier) size_t est « de rang inférieur » à int, on a
conversion (vers int ou unsigned, c'est selon); si size_t est de rang
supérieur (le même que unsigned long, par exemple), il n'y a pas de
conversion; si c'était un float, on aurait conversion en double; etc.
Ici aussi, pas mal de compilateurs vont avoir un traitement spécial lié au
nom printf; d'abord, en dehors de certains compilateurs programmés pour
donner des problèmes, un compilateur «utilisable» va forcer le type fonction
variadique même s'il n'est pas déclaré comme tel (il y a trop de programme
qui utilise printf sans la déclarer, c'est illégal mais c'est comme cela);
un autre classique (bien pratique ma fois) est de lancer un processus de
validation des compatibilités des paramètres de printf, cela donne des
warnings souvent utiles; d'autres compilateurs plus évolués vont remplacer
toute l'expression par raise(SIGSEGV) ou puts("4"); bien sûr, aucun de ces
traitements spéciaux ne sont requis par la norme, il s'agit ici purement de
qualité de l'implémentation, ce qui fait toute la différence entre le
compilateur utilisé et celui qui ne l'est pas.
Le compilateur passe ensuite à la génération du code d'appel d'une fonction
variadique (code qui peut être très différent d'un appel normal, par exemple
certains compilateurs passe les paramètres dans un ordre différent, sans
utiliser les registres, avec un paramètre spécial pour le nombre de
paramètres variables, avec un appel à une fonction d'aide $varcall, pour les
appels variadiques, bref plein de trucs possible...) Le char* est passé
comme tel; et le résultat de l'expression est passé conformément à son type
après promotion: par exemple, si c'est un unsigned long de taille double de
celle d'un int, il va utiliser deux fois plus de place...
Enfin, le compilateur attribue le type int au résultat, et on peut passer à
la suite.

La suite, qui est aussi la fin, c'est la compilation de l'instruction
«expression». En gros, le compilateur fait son ménage interne: par exemple,
il laisse tomber le résultat de l'appel à printf() qui n'est pas utilisé; il
peut aussi en profiter pour réaligner la pile de manière globale, c'est le
moment où la plupart des compilateurs vont effectivement passer à la phase
d'optimisation locale ou de géneration de code, etc.

Plus tard (mais toujours dans le premier temps), le compilateur va appeler
l'éditeur des liens, qui va créer l'image exécutable, et en particulier
résoudre les liens entre le bout de code que l'on vient de produire, les
deux objets que l'on a défini, et les fonctions de bibliothèques strlen,
printf et $varcall (en supposant qu'il n'y a eu aucune simplification, ni
interposition de raise ou puts).

On a fini avec le premier temps.


Dans un deuxième temps, ton programme s'exécute enfin, et on va supposer
qu'il passe dans le bout de code en question.
Préalablement, au démarrage du programme, un certain nombre de choses vont
se passer, le flux stdout va être initialisé, les deux objets chaînes vont
être effectivement alloués en mémoire, les bibliothèques dynamiques vont
être liées, etc.
Au moment du passage dans le code, la CPU va rencontrer l'appel à strlen, et
va appeler la fonction (qui va retourner 4). Éventuellement on a ensuite le
code de conversion du paramètre de type size_t. Puis va faire l'appel à
printf, avec comme paramètre un pointeur vers {'%', 'u', NL, 0} et d'autre
part le 4 (converti): dans la bibliothèque, le code de printf initialiser
l'appel de fonction variadique, va analyser la chaîne, repérer le %u, aller
chercher dans les paramètres passés une valeur de type unsigned (un 4, ou un
0, ou que sais-je, cela dépend de plein de facteurs, comme l'expliquait Marc
hier); ensuite il le convertit en chaîne de caractères (disons "4"), et
envoie le caractère '4' dans le flux stdout; ensuite il repère le caractère
NL, et l'envoie dasn le flux (ce qui va provoquer tout plein de choses, par
exemple que le 4 apparaît sur ton écran). Ensuite il atteint la fin de la
chaîne signalée par le caractère nul, termine l'appel de fonction variadique
et retourne au programme.


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



Non.

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



Pas du tout.


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

printf("%un",(unsigned)strlen("toto"));

est exécutée ?



Par rapport à ci-dessus, après l'appel à strlen, le résultat subit une
conversion forcée vers unsigned int (ce qui peut provoquer un appel de
fonction ou pas, l'émission de code ou pas, etc.)
Ensuite, au moment de l'évaluation des paramètres dans l'opération d'appel
de fonction variadique, le cas est plus déterministe, il n'y a pas de
promotion. Et plus bas, le paramètre est passé comme unsigned.

Dans le deuxième temps, au moment d'interpréter la conversion %u, print ira
chercher un unsigned, et donc trouvera toujours 4.


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 ?



Parce qu'il n'y a pas que des int dans la vie.


Et puis, je ne vois pas où il est dit dans la norme que les arguments
entiers sont convertis en int.



Nulle part car ce n'est pas ce qui se passe.

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.



Yep.


Mais la conversion

size_t (le type le plus large) -> int

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



Bin non. Si size_t est un alias pour unsigned char, c'est bien une
promotion.



Antoine
Avatar
Antoine Leca
En news:487d095d$0$18576$, candide va escriure:
Mais que se passe-t-il, étape par étape, quand l'instruction

printf("%un",strlen("toto"));

est exécutée ?



Il y a deux temps bien distincts.


Premier temps, la compilation.
Je suppose qu'auparavant, tu as mis les #include appropriés, ou à tout le
moins que les prototypes (de printf _et_ de strlen) sont correctement
déclarés.

D'abord, le compilateur évalue "toto", c'est un objet (d'où allocation) et
on lui colle son type, en l'occurence char[5] (ou const char[5], je ne
rentre pas dans ce détail-là).

Ensuite il évalue l'appel à la fonction strlen(); le nom strlen est évalué,
le compilateur remarque qu'il s'agit d'une fonction avec comme type
size_t(const char*); puis l'opération appel de fonction est évaluée, avec
comme opérandes d'un côté une fonction de l'autre un tableau: comme ni l'un
ni l'autre ne convienne aux règles de C, le compilateur réarrange les deux,
la fonction est convertie en pointeur vers elle, de type size_t(*)(const
char*); et l'argument de type char[5] est converti en char*, c'est le
classique passage de tableau; ensuite le compilateur passe aux règles de
conversion pour l'appel, et remarque qu'un char* peut être passé à une
fonction qui attend un const char*, donc pas de problème; le compilateur
peut donc maintenant (enfin) passer à la génération du code d'appel de
strlen; ici, pas mal de compilateur vont remarquer que strlen est une
fonction particulière ("built-in") et générer un code spécial, par exemple
expansion en ligne ("inline"), ou même remplacement par la valeur 4;
d'autres vont empiler, instruction d'appel de sous-programme, et dépiler;
les détails sont sans importance.
Enfin, le compilateur attribue le type size_t au résultat, et on peut passer
à la suite.

La suite, c'est "%un", je ne rentre pas dans les détails, idem "toto" sauf
pour le type char[4].

On arrive à l'appel de la fonction printf(,). Le nom printf est évalué, une
fonction de type int(const char*, ...). Ensuite l'opération d'appel de
fonction _variadique_: on a d'abord les mêmes transformations que ci-dessus,
d'un côté on a maintenant un int(*)(const char*, ...), et de l'autre le
tableau char[3] est converti en char*, qui est OK pour le paramètre attendu
comme const char*; pour l'expression, on applique les règles de promotions:
ainsi si le type (entier) size_t est « de rang inférieur » à int, on a
conversion (vers int ou unsigned, c'est selon); si size_t est de rang
supérieur (le même que unsigned long, par exemple), il n'y a pas de
conversion; si c'était un float, on aurait conversion en double; etc.
Ici aussi, pas mal de compilateurs vont avoir un traitement spécial lié au
nom printf; d'abord, en dehors de certains compilateurs programmés pour
donner des problèmes, un compilateur «utilisable» va forcer le type fonction
variadique même s'il n'est pas déclaré comme tel (il y a trop de programme
qui utilise printf sans la déclarer, c'est illégal mais c'est comme cela);
un autre classique (bien pratique ma fois) est de lancer un processus de
validation des compatibilités des paramètres de printf, cela donne des
warnings souvent utiles; d'autres compilateurs plus évolués vont remplacer
toute l'expression par raise(SIGSEGV) ou puts("4"); bien sûr, aucun de ces
traitements spéciaux ne sont requis par la norme, il s'agit ici purement de
qualité de l'implémentation, ce qui fait toute la différence entre le
compilateur utilisé et celui qui ne l'est pas.
Le compilateur passe ensuite à la génération du code d'appel d'une fonction
variadique (code qui peut être très différent d'un appel normal, par exemple
certains compilateurs passe les paramètres dans un ordre différent, sans
utiliser les registres, avec un paramètre spécial pour le nombre de
paramètres variables, avec un appel à une fonction d'aide $varcall, pour les
appels variadiques, bref plein de trucs possible...) Le char* est passé
comme tel; et le résultat de l'expression est passé conformément à son type
après promotion: par exemple, si c'est un unsigned long de taille double de
celle d'un int, il va utiliser deux fois plus de place...
Enfin, le compilateur attribue le type int au résultat, et on peut passer à
la suite.

La suite, qui est aussi la fin, c'est la compilation de l'instruction
«expression». En gros, le compilateur fait son ménage interne: par exemple,
il laisse tomber le résultat de l'appel à printf() qui n'est pas utilisé; il
peut aussi en profiter pour réaligner la pile de manière globale, c'est le
moment où la plupart des compilateurs vont effectivement passer à la phase
d'optimisation locale ou de géneration de code, etc.

Plus tard (mais toujours dans le premier temps), le compilateur va appeler
l'éditeur des liens, qui va créer l'image exécutable, et en particulier
résoudre les liens entre le bout de code que l'on vient de produire, les
deux objets que l'on a défini, et les fonctions de bibliothèques strlen,
printf et $varcall (en supposant qu'il n'y a eu aucune simplification, ni
interposition de raise ou puts).

On a fini avec le premier temps.


Dans un deuxième temps, ton programme s'exécute enfin, et on va supposer
qu'il passe dans le bout de code en question.
Préalablement, au démarrage du programme, un certain nombre de choses vont
se passer, le flux stdout va être initialisé, les deux objets chaînes vont
être effectivement alloués en mémoire, les bibliothèques dynamiques vont
être liées, etc.
Au moment du passage dans le code, la CPU va rencontrer l'appel à strlen, et
va appeler la fonction (qui va retourner 4). Éventuellement on a ensuite le
code de conversion du paramètre de type size_t. Puis va faire l'appel à
printf, avec comme paramètre un pointeur vers {'%', 'u', NL, 0} et d'autre
part le 4 (converti): dans la bibliothèque, le code de printf initialiser
l'appel de fonction variadique, va analyser la chaîne, repérer le %u, aller
chercher dans les paramètres passés une valeur de type unsigned (un 4, ou un
0, ou que sais-je, cela dépend de plein de facteurs, comme l'expliquait Marc
hier); ensuite il le convertit en chaîne de caractères (disons "4"), et
envoie le caractère '4' dans le flux stdout; ensuite il repère le caractère
NL, et l'envoie dasn le flux (ce qui va provoquer tout plein de choses, par
exemple que le 4 apparaît sur ton écran). Ensuite il atteint la fin de la
chaîne signalée par le caractère nul, termine l'appel de fonction variadique
et retourne au programme.


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



Non.

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



Pas du tout.


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

printf("%un",(unsigned)strlen("toto"));

est exécutée ?



Par rapport à ci-dessus, après l'appel à strlen, le résultat subit une
conversion forcée vers unsigned int (ce qui peut provoquer un appel de
fonction ou pas, l'émission de code ou pas, etc.)
Ensuite, au moment de l'évaluation des paramètres dans l'opération d'appel
de fonction variadique, le cas est plus déterministe, il n'y a pas de
promotion. Et plus bas, le paramètre est passé comme unsigned.

Dans le deuxième temps, au moment d'interpréter la conversion %u, print ira
chercher un unsigned, et donc trouvera toujours 4.


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 ?



Parce qu'il n'y a pas que des int dans la vie.


Et puis, je ne vois pas où il est dit dans la norme que les arguments
entiers sont convertis en int.



Nulle part car ce n'est pas ce qui se passe.

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.



Yep.


Mais la conversion

size_t (le type le plus large) -> int

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



Bin non. Si size_t est un alias pour unsigned char, c'est bien une
promotion.



Antoine
Avatar
candide
Marc Espie a écrit :
In article <487d095d$0$18576$,
candide wrote:

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

printf("%un",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 ?



Non, elle est plus ou moins laissee telle quelle, en particulier en terme
de taille



Je ne trouve pas ton discours très précis.


(sinon, tu fais comment pour passer des long a des fonctions
variadiques, gros malin ?)



Je suppose que c'est en cherchant le spécificateur %l, non ?

Mais alors à quoi servent les promotions des arguments par défaut
("default argument promotions") ? Bref, je comprends rien au processus
d'exécution de printf : quand y-a-t-il conversion et en fonction de quoi
cette(ces) conversion(s) est(sont)-elle(s) faite(s) ?

Quand j'ai

printf("%un",strlen("toto"));

strlen("toto") est évalué puis en fonction du spécificateur %u, cette
valeur est convertie en unsigned int, c'est ça ? Si c'est ça, je vois ne
vois pas où ces fout*** "default argument promotions" interviennent. Et
ça correspondrait à ce que j'ai compris : les promotions promeuvent (et
ne dégradent pas donc).
Avatar
candide
Marc Espie a écrit :
In article <487d095d$0$18576$,
candide wrote:

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

printf("%un",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 ?



Non, elle est plus ou moins laissee telle quelle, en particulier en terme
de taille



Je ne trouve pas ton discours très précis.


(sinon, tu fais comment pour passer des long a des fonctions
variadiques, gros malin ?)



Je suppose que c'est en cherchant le spécificateur %l, non ?

Mais alors à quoi servent les promotions des arguments par défaut
("default argument promotions") ? Bref, je comprends rien au processus
d'exécution de printf : quand y-a-t-il conversion et en fonction de quoi
cette(ces) conversion(s) est(sont)-elle(s) faite(s) ?

Quand j'ai

printf("%un",strlen("toto"));

strlen("toto") est évalué puis en fonction du spécificateur %u, cette
valeur est convertie en unsigned int, c'est ça ? Si c'est ça, je vois ne
vois pas où ces fout*** "default argument promotions" interviennent. Et
ça correspondrait à ce que j'ai compris : les promotions promeuvent (et
ne dégradent pas donc).
Avatar
candide
Antoine Leca a écrit :
En news:487d095d$0$18576$, candide va escriure:
Mais que se passe-t-il, étape par étape, quand l'instruction

printf("%un",strlen("toto"));

est exécutée ?



Il y a deux temps bien distincts.


Premier temps, la compilation.



J'ai répondu tout-à-l'heure à un autre message sans recharger mon
lecteur et donc je découvre maintenant ta copieuse réponse dont je te
remercie et que je vais analyser maintenant.
Avatar
candide
Antoine Leca a écrit :
En news:487d095d$0$18576$, candide va escriure:
Mais que se passe-t-il, étape par étape, quand l'instruction

printf("%un",strlen("toto"));

est exécutée ?



Il y a deux temps bien distincts.


Premier temps, la compilation.



J'ai répondu tout-à-l'heure à un autre message sans recharger mon
lecteur et donc je découvre maintenant ta copieuse réponse dont je te
remercie et que je vais analyser maintenant.
Avatar
espie
In article <487df5be$0$11997$,
candide wrote:
Marc Espie a écrit :
In article <487d095d$0$18576$,
candide wrote:

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

printf("%un",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 ?



Non, elle est plus ou moins laissee telle quelle, en particulier en terme
de taille



Je ne trouve pas ton discours très précis.



Non, c'est la suite (que tu n'as pas citee) qui est precise.


(sinon, tu fais comment pour passer des long a des fonctions
variadiques, gros malin ?)



Je suppose que c'est en cherchant le spécificateur %l, non ?



Evidemment, si tu confonds les specificites du fonctionnement de printf
avec le passage de parametres a une fonction, tu ne vas pas t'en servir.

Principe de base du C: la bibliotheque standard n'est pas magique. Elle
est implementable en C standard (mais non portable) si tu te donnes des
primitives d'entrees-sorties raisonnables. C'est lie a la genese du C, et
ce principe est encore aujourd'hui respecte.

En particulier, le seul mecanisme dont tu disposes pour avoir des fonctions
a nombre variables de parametres, c'est l'ellipse. Pour invoquer printf,
tu invoques juste une fonction f de prototype
int f(const char *, ...);

C'est le *seul* mecanisme dont dispose le langage pour donner une semantique
a cet appel de fonction, il n'y a pas de magie specifique a printf.
[ ca ne veut pas dire qu'une implementation n'a pas le droit de profiter
du caractere magique de printf, juste que la semantique du langage est
definie pour que ca fonctionne meme sans magie ]

Mais alors à quoi servent les promotions des arguments par défaut
("default argument promotions") ?



Elles servent a rester compatible avec du C sans prototype, ou les choses
fonctionnaient comme cela. Ca sert a s'assurer que, meme en l'absence
complete de prototype, on a facilement quelque chose qui marche sur des
appels simples de fonction a base de char, short, ou int.

long et double sortent du lot, et necessitent des precautions particulieres:
c'est un compromis d'efficacite.

Rappel: int est sense representer le type entier *natif* du systeme, a
savoir celui qui represente le meilleur compromis en terme d'efficacite.
C'est normal que, par defaut, les parametres entiers soient promus vers
celui-ci. Sur pas mal d'archis, passer explicitement des trucs plus courts
a une fonction necessite des contorsions couteuses en temps.


Bref, je comprends rien au processus
d'exécution de printf : quand y-a-t-il conversion et en fonction de quoi
cette(ces) conversion(s) est(sont)-elle(s) faite(s) ?

Quand j'ai

printf("%un",strlen("toto"));

strlen("toto") est évalué puis en fonction du spécificateur %u, cette
valeur est convertie en unsigned int, c'est ça ? Si c'est ça, je vois ne
vois pas où ces fout*** "default argument promotions" interviennent. Et
ça correspondrait à ce que j'ai compris : les promotions promeuvent (et
ne dégradent pas donc).



si tu fais

short d = 42;
printf("%h", d);

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


Dans l'implementation de printf que j'ai sous la main, je vois d'ailleurs:

switch (typetable[n]) {
case T_UNUSED:
case T_CHAR:
case T_U_CHAR:
case T_SHORT:
case T_U_SHORT:
case T_INT:
(*argtable)[n].intarg = va_arg(ap, int);
break;
...
}

qui correspond precisement au fait que tous les parametres plus courts sont
promus en int au moment de l'appel.
Avatar
candide
Antoine Leca a écrit :

Enfin, le compilateur attribue le type size_t au résultat, et on peut passer
à la suite.



Je note ce point important. Ainsi le type size_t du résultat n'est pas
ignoré, il est mis de côté en attendant la suite.



(...) pour l'expression, on applique les règles de promotions:



l'expression ? de quelle expression tu parles ? strlen("toto") ? je
croyais qu'on n'en parlait plus de celle-là maintenant que son type a
été repéré par le compilateur.



> (...) pour l'expression, on applique les règles de promotions:

Tu parles bien des "default argument promotions", n'est ce pas ?



ainsi si le type (entier) size_t est « de rang inférieur » à int, on a





Mais de toute façon, je ne comprends pas pourquoi tu raisonnes en terme
de rang. La norme C90 (je n'ai pas lu C99) définit la promotion
numérique d'abord en terme de type et accessoirement en terme de rang :


6.2.1.1 (C90)
A char, a short int, or an int bit-field, or their signed or
unsigned varieties, or an object that has enumeration type, may be
used in an expression wherever an int or unsigned int may be used.
If an int can represent all values of the original type, the value
is converted to an int; otherwise it is converted to an unsigned
int. These are called the integral promotions.


Mais peut-être parles-tu de rang au sens de "integer conversion rank "
dans la norme C99 auquel cas je vais avoir peine à décoder tout ce que
tu dis.



conversion (vers int ou unsigned, c'est selon); si size_t est de rang
supérieur (le même que unsigned long, par exemple), il n'y a pas de
conversion; (...)



Je ne vois pas où la norme quand elle parle de promotion numérique
dit qu'un unsigned int reste inchangé. Ce qui me gêne c'est que les
promotions numériques ne concernent pas les int, unsigned int, etc. Ça
concerne juste char, short, etc, cf. extrait ci-dessus. Si comme tu les
dis ci-dessous, size_t est un alias de unsigned char alors oui, il y a
automatquement conversion ou je dis encore une connerie ?


Par ailleurs, si j'ai bien compris ton exposé, jusqu'à présent le
spécificateur de conversion %u n'a pas été utilisé par le compilateur,
vrai ?


Le compilateur passe ensuite à la génération du code d'appel d'une fonction
variadique



>[...]

(...) et le résultat de l'expression est passé conformément à son type




http://www.perdu.com/

À quoi tu te réfères :

*) de quelle expression parles-tu ? encore de strlen("toto") ?
*) "l'expression est passé" ? est passé à qui ? à printf() ?

après promotion: par exemple, si c'est un unsigned long de taille double de



http://www.perdu.com/

Tu parles de ce qui représente size_t ?


celle d'un int, il va utiliser deux fois plus de place...



Que vient faire le int ici. C'est enfin à cause du spécificateur de
conversion %u ? Parce que pour l'instant, je ne vois pas où il est
intervenu.


Il y a peut-être un problème de vocabulaire : le terme "conversion" est
employé dans deux contextes différents : d'une part, conversion au sens
de conversion d'une valeur d'un type dans un autre et d'autre part, les
conversions opérées par les spécificateurs de conversion, les machins
%d, %u, %f, etc.


Enfin, le compilateur attribue le type int au résultat, et on peut passer à
la suite.




http://www.intelligent.com/

OK, là tu parles du retour de printf je suppose.



(...) Puis va faire l'appel à
printf, avec comme paramètre un pointeur vers {'%', 'u', NL, 0} et d'autre
part le 4 (converti): (...)



"converti" en fonction (cf. ce que tu as dit plus haut) du rang de size_t ?


l'appel de fonction variadique, va analyser la chaîne, repérer le %u, aller
chercher dans les paramètres passés une valeur de type unsigned



http://www.perdu.com/

Je comprends pas : il y a encore une conversion.

Je comprends pas "aller chercher dans les paramètres passés". Il n'y a
plus de paramètres, il n'y a que des arguments, en l'occurrence le
pointeur vers char et la valeur 4, de type par exemple unsigned long. Je
comprends pas ce que ça veut dire aller chercher une valeur de type
unsigned.

Et est-la première fois que l'on tient compte de %u ? je suppose que non.




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

printf("%un",(unsigned)strlen("toto"));

est exécutée ?



Par rapport à ci-dessus, après l'appel à strlen, le résultat subit une
conversion forcée vers unsigned int (ce qui peut provoquer un appel de
fonction ou pas, l'émission de code ou pas, etc.)
Ensuite, au moment de l'évaluation des paramètres dans l'opération d'appel
de fonction variadique, le cas est plus déterministe, il n'y a pas de
promotion.



Et pourquoi ? tu disais tout à l'heure dans le premier exemple que dans
une fonction variadique, les arguments par défaut subissent la promotion.


promotion. Et plus bas, le paramètre est passé comme unsigned.



"plus bas" ? à l'exécution (désolé des questions triviales mais j'ai
besoin d'un GPS)





Dans le deuxième temps, au moment d'interpréter la conversion %u, print ira
chercher un unsigned, et donc trouvera toujours 4.




On va vers le nord ou vers le sud ? ;) on en est à la compilation ou à
l'exécution ?



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 ?



Parce qu'il n'y a pas que des int dans la vie.



Sauf que j'avais compris que les promotions se faisaient vers des int.



Bin non. Si size_t est un alias pour unsigned char, c'est bien une
promotion.



L'apothéose !! size_t un alias de unsigned char ?????? Heureusement que
je suis pas cardiaque ! Mais je reprends mes esprits et je me dis "ah
ben oui mais c'est bien sûr !" Je crois que depuis le début, je confonds
size_t et unsigned long sous prétexte que c'est en size_t que l'on peut
exprimer la taille du plus grand objet possible. Oui, je sais c'est
grave ;) .

Mais pourquoi la norme dit-elle seulement que size_t est un "type entier
non signé" et nous fait-elle des cachoteries ?


Et merci pour tes explications, j'ai le sentiment d'avoir un petit peu
avancé.
1 2 3 4 5