D'abord, un point de vocabulaire : en français, parle-t-on de "promotion
entière" ou de "promotion intégrale" ?
D'autre part, le passage de la norme qui parle de cette question est, à
mon sens, trop fortement obscurci par une note de bas de page. Plus
précisément, il est dit :
------------------------8<-----------------------------
6.3.1.1p2
(...)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 integer promotions.(48)
La note de bas de page :
(48) -- The integer promotions are applied only: as part of the usual
arithmetic conversions, to certain argument expressions, to the operands
of the unary +, -, and ~ operators, and to both operands of the
shift operators, as specified by their respective subclauses.
------------------------>8-----------------------------
Si j'ai bien lu la norme, les conversions arithmétiques usuelles
s'appliquent uniquement à des opérateurs binaires et il faut regarder
dans la norme au cas par cas lesquels sont concernés.
Concernant les "argument expressions", bon je suppose les expressions
qui sont arguments de fonctions, lesquelles sont concernées ? K&R parle
de ceci :
------------------------8<-----------------------------
In the absence of a function prototype, char and short become int,
------------------------>8-----------------------------
c'est à ça qu'il est fait allusion ?
Je crois que beaucoup de livres de C n'ont pas tenu compte de cette note
de pas de page. Par exemple, dans le livre de Delannoy, je lis (page
78, §3.1) :
------------------------8<-----------------------------
"Aucun opérateur n'accepte d'opérandes de type char ou short."
------------------------>8-----------------------------
C'est vrai ou pas ?
De même dans le livre de Braquelaire, je lis :
------------------------8<-----------------------------
"Lors de l'évaluation d'une expression, un opérande d'un type intégral
caractère, entier court, champ de bits ou énumération est converti dans
le type int ou dans le type unsigned int si le type int n'est pas assez
grand pour représenter toutes les valeurs de son type. Cette conversion
s'appelle promotion intégrale. "
------------------------>8-----------------------------
mais aucune allusion à la note de bas de page.
Plus généralement, avez-vous des exemples simples où la promotion
entière ne s'applique pas ?
Il suffit de regarder l'assembleur généré par les compilateurs pour s'apercevoir que tous créent des variables temporaires pour ce faire. J'ai fait des compilateurs. Je sais de quoi je parle. Explique-moi comment faire un passage par valeur sans créer une variable temporaire sur la pile :-).
Soit en utilisant des registres, soit en utilisant une pile alternée (genre les fenêtres de registres de certains RISC des années 90 ; je n'ai pas les noms en tête et suis trop flemmard pour chercher), soit en simulant le passage par valeur au moyen d'un passage par référence suivi d'une copie locale (très utilisé pour les structures). Ce qui fait trois métodes différentes en 10 secondes et sans chercher.
La 3ème méthode revient à créer une variable temporaire sur la pile puisque la copie est locale. J'ai déjà répondu à l'utilisation des registres. C'est une possibilité mais, à l'époque (dans les années 80), les machines pour lesquelles je faisais des compilos ne disposaient pas de beaucoup de registres et on les réservait à autre chose (comme garder des résultats de calculs intermédiaires pour les optimiser). Pour les structures, le plus simple est quand même de passer par référence constante plutôt que par valeur (ça devrait être le passage par défaut en C++, mais la compatibilité avec C a fait que...).
Et avec une minute de plus (c-à-d en relisant le fil), il y a aussi le fait de ne rien créer du tout, soit parce que le compilo « voit » que la conversion est inutile et que l'on peut économiser (rien n'interdit de faire une opération sur 8 bits si le résultat tient dans cette taille, et d'ailleurs beaucoup de compilos ne vont pas s'en priver), soit plus fort parce que l'optimiseur fait une analyse globale et élimine purement et simplement l'opération en question (et sans analyse globale, n'importe quel compilateur C réduit les constantes, et zou, y'a pù d'promotion au final ; OK, il faut un optimisateur global pour faire cela au travers d'un appel de fonction, mais le principe reste le même).
Faire une opération sur 8 bits peut s'avérer beaucoup plus coûteux que la faire sur 32 bits. Tout dépend du jeux d'instructions de la machine cible. Qu'entends-tu par "réduire les constantes" ? Il me semble que la deuxième partie de ton paragraphe n'a pas grand chose à voir avec le passage d'argument par valeur.
J'ai fait un optimiseur global (basé sur les algorithmes de Morel et Renvoise) et, dans ce cadre, on peut faire effectivement des choses merveilleuses et d'ailleurs bien plus optimisées que ne le fera jamais un programmeur en assembleur...
Wykaaa
Antoine Leca a écrit :
En news:485becb1$0$913$ba4acef3@news.orange.fr, Wykaaa va escriure:
Il suffit de regarder l'assembleur généré par les compilateurs pour
s'apercevoir que tous créent des variables temporaires pour ce faire.
J'ai fait des compilateurs. Je sais de quoi je parle.
Explique-moi comment faire un passage par valeur sans créer une
variable temporaire sur la pile :-).
Soit en utilisant des registres, soit en utilisant une pile alternée (genre
les fenêtres de registres de certains RISC des années 90 ; je n'ai pas les
noms en tête et suis trop flemmard pour chercher), soit en simulant le
passage par valeur au moyen d'un passage par référence suivi d'une copie
locale (très utilisé pour les structures).
Ce qui fait trois métodes différentes en 10 secondes et sans chercher.
La 3ème méthode revient à créer une variable temporaire sur la pile
puisque la copie est locale.
J'ai déjà répondu à l'utilisation des registres. C'est une possibilité
mais, à l'époque (dans les années 80), les machines pour lesquelles je
faisais des compilos ne disposaient pas de beaucoup de registres et on
les réservait à autre chose (comme garder des résultats de calculs
intermédiaires pour les optimiser).
Pour les structures, le plus simple est quand même de passer par
référence constante plutôt que par valeur (ça devrait être le passage
par défaut en C++, mais la compatibilité avec C a fait que...).
Et avec une minute de plus (c-à-d en relisant le fil), il y a aussi le fait
de ne rien créer du tout, soit parce que le compilo « voit » que la
conversion est inutile et que l'on peut économiser (rien n'interdit de faire
une opération sur 8 bits si le résultat tient dans cette taille, et
d'ailleurs beaucoup de compilos ne vont pas s'en priver), soit plus fort
parce que l'optimiseur fait une analyse globale et élimine purement et
simplement l'opération en question (et sans analyse globale, n'importe quel
compilateur C réduit les constantes, et zou, y'a pù d'promotion au final ;
OK, il faut un optimisateur global pour faire cela au travers d'un appel de
fonction, mais le principe reste le même).
Faire une opération sur 8 bits peut s'avérer beaucoup plus coûteux que
la faire sur 32 bits. Tout dépend du jeux d'instructions de la machine
cible.
Qu'entends-tu par "réduire les constantes" ?
Il me semble que la deuxième partie de ton paragraphe n'a pas grand
chose à voir avec le passage d'argument par valeur.
J'ai fait un optimiseur global (basé sur les algorithmes de Morel et
Renvoise) et, dans ce cadre, on peut faire effectivement des choses
merveilleuses et d'ailleurs bien plus optimisées que ne le fera jamais
un programmeur en assembleur...
Il suffit de regarder l'assembleur généré par les compilateurs pour s'apercevoir que tous créent des variables temporaires pour ce faire. J'ai fait des compilateurs. Je sais de quoi je parle. Explique-moi comment faire un passage par valeur sans créer une variable temporaire sur la pile :-).
Soit en utilisant des registres, soit en utilisant une pile alternée (genre les fenêtres de registres de certains RISC des années 90 ; je n'ai pas les noms en tête et suis trop flemmard pour chercher), soit en simulant le passage par valeur au moyen d'un passage par référence suivi d'une copie locale (très utilisé pour les structures). Ce qui fait trois métodes différentes en 10 secondes et sans chercher.
La 3ème méthode revient à créer une variable temporaire sur la pile puisque la copie est locale. J'ai déjà répondu à l'utilisation des registres. C'est une possibilité mais, à l'époque (dans les années 80), les machines pour lesquelles je faisais des compilos ne disposaient pas de beaucoup de registres et on les réservait à autre chose (comme garder des résultats de calculs intermédiaires pour les optimiser). Pour les structures, le plus simple est quand même de passer par référence constante plutôt que par valeur (ça devrait être le passage par défaut en C++, mais la compatibilité avec C a fait que...).
Et avec une minute de plus (c-à-d en relisant le fil), il y a aussi le fait de ne rien créer du tout, soit parce que le compilo « voit » que la conversion est inutile et que l'on peut économiser (rien n'interdit de faire une opération sur 8 bits si le résultat tient dans cette taille, et d'ailleurs beaucoup de compilos ne vont pas s'en priver), soit plus fort parce que l'optimiseur fait une analyse globale et élimine purement et simplement l'opération en question (et sans analyse globale, n'importe quel compilateur C réduit les constantes, et zou, y'a pù d'promotion au final ; OK, il faut un optimisateur global pour faire cela au travers d'un appel de fonction, mais le principe reste le même).
Faire une opération sur 8 bits peut s'avérer beaucoup plus coûteux que la faire sur 32 bits. Tout dépend du jeux d'instructions de la machine cible. Qu'entends-tu par "réduire les constantes" ? Il me semble que la deuxième partie de ton paragraphe n'a pas grand chose à voir avec le passage d'argument par valeur.
J'ai fait un optimiseur global (basé sur les algorithmes de Morel et Renvoise) et, dans ce cadre, on peut faire effectivement des choses merveilleuses et d'ailleurs bien plus optimisées que ne le fera jamais un programmeur en assembleur...
Wykaaa
Antoine Leca
En news:48602cbf$0$841$, Wykaaa va escriure:
Antoine Leca a écrit :
Wykaaa va escriure:
Explique-moi comment faire un passage par valeur sans créer une variable temporaire sur la pile :-).
Soit en utilisant des registres, soit en utilisant une pile alternée, soit en simulant le passage par valeur au moyen d'un passage par référence suivi d'une copie locale.
La 3ème méthode revient à créer une variable temporaire sur la pile puisque la copie est locale.
Pourquoi sur la pile ? En fait avec une copie locale c'est l'optimisateur normal (celui qui agit au niveau de la fonction) qui décide, si la structure n'est pas effectivement utilisée il n'y a pas de copie locale, si seulement un membre est utilisé il peut faire une simple lecture de ce seul membre, en le gardant en registre seulement si c'est nécessaire, etc.
mais, à l'époque (dans les années 80), les machines pour lesquelles je faisais des compilos ne disposaient pas de beaucoup de registres et on les réservait à autre chose
Un des gros problèmes (d'aucuns diraient avantage !) du C est que la gamme des machines cibles va (dans les années 80 déjà) des microcontrôleurs ultra simplistes (l'archétype étant probablement le 8051), aux super-ordinateurs avec plusieurs unités de traitements dont l'ordonnancement est géré par le compilateur (contrairement aux architectures SMP actuelles).
Pour les structures, le plus simple est quand même de passer par référence constante plutôt que par valeur
Plus simple pour qui ? Dans un environnement multi-tâches (/multithread/ pour parler chébran), avec un passage par référence (supposée) constante, tu crées une situation de compétition (/race condition/) si par hasard ta structure doit être modifiée dans une autre partie du programme; des fois c'est utile, des fois c'est sans effet, mais parfois, en particulier s'il y a rechargement à plusieurs reprises de la « référence constante » qui ne l'est pas en réalité, tu peux avoir des bogues TRÈS pénibles à diagnostiquer...
(ça devrait être le passage par défaut en C++,
Si on pouvait éviter de parler de C++ lorsque ce n'est pas nécessaire...
Faire une opération sur 8 bits peut s'avérer beaucoup plus coûteux que la faire sur 32 bits.
« Peut ». Dans le même genre, faire une opération sur 32 bits « peut » s'avérer plus coûteux que la faire sur 18 bits. Cela dépend de la machine cible, et cela fait partie des variables auxquelles je faisait allusion ci-dessus, un des « problèmes » du C.
Maintenant, sur une machine où les opérations sur 8 bits peuvent s'avérer beaucoup plus coûteuses que de le faire sur 32 bits, si un compilateur s'amuse à mettre en place les optimisations dont je parlais, là je dis que c'est un mauvais compilateur et qu'il ne vaut la peine de continuer à en parler.
Qu'entends-tu par "réduire les constantes" ?
Dans un compilateur, remplacer une suite d'opérations par le résultat qu'elle donne, et ne pas générer les instructions qui font les opérations, seulement mettre la valeur résultat directement à la bonne place. Et avant que Vincent ne rajoute une réponse ;-), je précise que si l'on veut faire cela pour les opérations en virgule flottante, l'équipe qui écrit le compilateur doit avoir une excellente connaissance de l'architecture cible d'une part, et des (nombreux) travaux sur la théorie *et* la pratique des opérations en virgule flottante d'autre part, et aussi avoir la pleine conscience de limiter la future portabilité de leur produit sur les architectures futures (histoire de ne pas tomber dans l'écueil signalé ci-dessus, le compilateur sans intérêt); en gros, cela signifie avoir un contrat bien juteux qui justifie de pousser dans cette voie de recherche de performances, et une option pour désactiver cette optimisation.
[un optimiseur global], on peut faire effectivement des choses merveilleuses et d'ailleurs bien plus optimisées que ne le fera jamais un programmeur en assembleur...
Ce sont deux axes orthogonaux : l'optimisateur global va te donner des pistes d'optimisation (par exemple s'apercevoir que tel ou tel code ne sert à rien et peut donc être purement et simplement supprimé ; ce genre d'optimisation « tue » les tests de performances de compilateurs, car le résultat est infiniment meilleur que quoi que ce soit d'autre...) De son côté le fait pour un spécialiste d'une architecture matérielle de programmer en assembleur lui permet de grapiller deci delà des cycles ou des instructions qui s'ils sont au bon endroit peuvent apporter un gain significatif sur le résultat final (cela peut aussi faire exploser la fusée).
Antoine
En news:48602cbf$0$841$ba4acef3@news.orange.fr, Wykaaa va escriure:
Antoine Leca a écrit :
Wykaaa va escriure:
Explique-moi comment faire un passage par valeur sans créer une
variable temporaire sur la pile :-).
Soit en utilisant des registres, soit en utilisant une pile alternée,
soit en simulant le passage par valeur au moyen d'un passage par
référence suivi d'une copie locale.
La 3ème méthode revient à créer une variable temporaire sur la pile
puisque la copie est locale.
Pourquoi sur la pile ? En fait avec une copie locale c'est l'optimisateur
normal (celui qui agit au niveau de la fonction) qui décide, si la structure
n'est pas effectivement utilisée il n'y a pas de copie locale, si seulement
un membre est utilisé il peut faire une simple lecture de ce seul membre, en
le gardant en registre seulement si c'est nécessaire, etc.
mais, à l'époque (dans les années 80), les machines pour lesquelles je
faisais des compilos ne disposaient pas de beaucoup de registres et on
les réservait à autre chose
Un des gros problèmes (d'aucuns diraient avantage !) du C est que la gamme
des machines cibles va (dans les années 80 déjà) des microcontrôleurs ultra
simplistes (l'archétype étant probablement le 8051), aux super-ordinateurs
avec plusieurs unités de traitements dont l'ordonnancement est géré par le
compilateur (contrairement aux architectures SMP actuelles).
Pour les structures, le plus simple est quand même de passer par
référence constante plutôt que par valeur
Plus simple pour qui ?
Dans un environnement multi-tâches (/multithread/ pour parler chébran), avec
un passage par référence (supposée) constante, tu crées une situation de
compétition (/race condition/) si par hasard ta structure doit être modifiée
dans une autre partie du programme; des fois c'est utile, des fois c'est
sans effet, mais parfois, en particulier s'il y a rechargement à plusieurs
reprises de la « référence constante » qui ne l'est pas en réalité, tu peux
avoir des bogues TRÈS pénibles à diagnostiquer...
(ça devrait être le passage par défaut en C++,
Si on pouvait éviter de parler de C++ lorsque ce n'est pas nécessaire...
Faire une opération sur 8 bits peut s'avérer beaucoup plus coûteux que
la faire sur 32 bits.
« Peut ». Dans le même genre, faire une opération sur 32 bits « peut »
s'avérer plus coûteux que la faire sur 18 bits. Cela dépend de la machine
cible, et cela fait partie des variables auxquelles je faisait allusion
ci-dessus, un des « problèmes » du C.
Maintenant, sur une machine où les opérations sur 8 bits peuvent s'avérer
beaucoup plus coûteuses que de le faire sur 32 bits, si un compilateur
s'amuse à mettre en place les optimisations dont je parlais, là je dis que
c'est un mauvais compilateur et qu'il ne vaut la peine de continuer à en
parler.
Qu'entends-tu par "réduire les constantes" ?
Dans un compilateur, remplacer une suite d'opérations par le résultat
qu'elle donne, et ne pas générer les instructions qui font les opérations,
seulement mettre la valeur résultat directement à la bonne place.
Et avant que Vincent ne rajoute une réponse ;-), je précise que si l'on veut
faire cela pour les opérations en virgule flottante, l'équipe qui écrit le
compilateur doit avoir une excellente connaissance de l'architecture cible
d'une part, et des (nombreux) travaux sur la théorie *et* la pratique des
opérations en virgule flottante d'autre part, et aussi avoir la pleine
conscience de limiter la future portabilité de leur produit sur les
architectures futures (histoire de ne pas tomber dans l'écueil signalé
ci-dessus, le compilateur sans intérêt); en gros, cela signifie avoir un
contrat bien juteux qui justifie de pousser dans cette voie de recherche de
performances, et une option pour désactiver cette optimisation.
[un optimiseur global], on peut faire effectivement des choses
merveilleuses et d'ailleurs bien plus optimisées que ne le fera jamais
un programmeur en assembleur...
Ce sont deux axes orthogonaux : l'optimisateur global va te donner des
pistes d'optimisation (par exemple s'apercevoir que tel ou tel code ne sert
à rien et peut donc être purement et simplement supprimé ; ce genre
d'optimisation « tue » les tests de performances de compilateurs, car le
résultat est infiniment meilleur que quoi que ce soit d'autre...)
De son côté le fait pour un spécialiste d'une architecture matérielle de
programmer en assembleur lui permet de grapiller deci delà des cycles ou des
instructions qui s'ils sont au bon endroit peuvent apporter un gain
significatif sur le résultat final (cela peut aussi faire exploser la
fusée).
Explique-moi comment faire un passage par valeur sans créer une variable temporaire sur la pile :-).
Soit en utilisant des registres, soit en utilisant une pile alternée, soit en simulant le passage par valeur au moyen d'un passage par référence suivi d'une copie locale.
La 3ème méthode revient à créer une variable temporaire sur la pile puisque la copie est locale.
Pourquoi sur la pile ? En fait avec une copie locale c'est l'optimisateur normal (celui qui agit au niveau de la fonction) qui décide, si la structure n'est pas effectivement utilisée il n'y a pas de copie locale, si seulement un membre est utilisé il peut faire une simple lecture de ce seul membre, en le gardant en registre seulement si c'est nécessaire, etc.
mais, à l'époque (dans les années 80), les machines pour lesquelles je faisais des compilos ne disposaient pas de beaucoup de registres et on les réservait à autre chose
Un des gros problèmes (d'aucuns diraient avantage !) du C est que la gamme des machines cibles va (dans les années 80 déjà) des microcontrôleurs ultra simplistes (l'archétype étant probablement le 8051), aux super-ordinateurs avec plusieurs unités de traitements dont l'ordonnancement est géré par le compilateur (contrairement aux architectures SMP actuelles).
Pour les structures, le plus simple est quand même de passer par référence constante plutôt que par valeur
Plus simple pour qui ? Dans un environnement multi-tâches (/multithread/ pour parler chébran), avec un passage par référence (supposée) constante, tu crées une situation de compétition (/race condition/) si par hasard ta structure doit être modifiée dans une autre partie du programme; des fois c'est utile, des fois c'est sans effet, mais parfois, en particulier s'il y a rechargement à plusieurs reprises de la « référence constante » qui ne l'est pas en réalité, tu peux avoir des bogues TRÈS pénibles à diagnostiquer...
(ça devrait être le passage par défaut en C++,
Si on pouvait éviter de parler de C++ lorsque ce n'est pas nécessaire...
Faire une opération sur 8 bits peut s'avérer beaucoup plus coûteux que la faire sur 32 bits.
« Peut ». Dans le même genre, faire une opération sur 32 bits « peut » s'avérer plus coûteux que la faire sur 18 bits. Cela dépend de la machine cible, et cela fait partie des variables auxquelles je faisait allusion ci-dessus, un des « problèmes » du C.
Maintenant, sur une machine où les opérations sur 8 bits peuvent s'avérer beaucoup plus coûteuses que de le faire sur 32 bits, si un compilateur s'amuse à mettre en place les optimisations dont je parlais, là je dis que c'est un mauvais compilateur et qu'il ne vaut la peine de continuer à en parler.
Qu'entends-tu par "réduire les constantes" ?
Dans un compilateur, remplacer une suite d'opérations par le résultat qu'elle donne, et ne pas générer les instructions qui font les opérations, seulement mettre la valeur résultat directement à la bonne place. Et avant que Vincent ne rajoute une réponse ;-), je précise que si l'on veut faire cela pour les opérations en virgule flottante, l'équipe qui écrit le compilateur doit avoir une excellente connaissance de l'architecture cible d'une part, et des (nombreux) travaux sur la théorie *et* la pratique des opérations en virgule flottante d'autre part, et aussi avoir la pleine conscience de limiter la future portabilité de leur produit sur les architectures futures (histoire de ne pas tomber dans l'écueil signalé ci-dessus, le compilateur sans intérêt); en gros, cela signifie avoir un contrat bien juteux qui justifie de pousser dans cette voie de recherche de performances, et une option pour désactiver cette optimisation.
[un optimiseur global], on peut faire effectivement des choses merveilleuses et d'ailleurs bien plus optimisées que ne le fera jamais un programmeur en assembleur...
Ce sont deux axes orthogonaux : l'optimisateur global va te donner des pistes d'optimisation (par exemple s'apercevoir que tel ou tel code ne sert à rien et peut donc être purement et simplement supprimé ; ce genre d'optimisation « tue » les tests de performances de compilateurs, car le résultat est infiniment meilleur que quoi que ce soit d'autre...) De son côté le fait pour un spécialiste d'une architecture matérielle de programmer en assembleur lui permet de grapiller deci delà des cycles ou des instructions qui s'ils sont au bon endroit peuvent apporter un gain significatif sur le résultat final (cela peut aussi faire exploser la fusée).