il suffisait de m'en parler plus tôt, j'aurai pas insisté. Je n'ai pas la science infuseconstexpr int
il suffisait de m'en parler plus tôt, j'aurai pas insisté. Je n'ai pas la science infuseconstexpr int
Et le programme, à ton avis, il est composé de quoi au final, si ce n'est de données et d'instructions processeur
Dés que le processeur doit utiliser une valeur quelconque issue du programme, il doit bien la trouver quelque par bon sang de bon soir. Il ne peut pas la trouver dans l'air du temps ou dans la phase de la lune!!!
Ca je suis bien d'accord. Mais le compilateur va traduire toutes tes valeurs et tes instructions en code binaire qui sera compréhensible par le processeur. Et si le compilateur voit la valeur 3 quelque part, hé bien, il va inscrire cette valeur soit dans un registre, soit dans la pile, soit à une adresse mémoire quelconque pour que le processeur puisse la retrouver d'une manière ou d'une autre.c'est pourtant pas compliqué: le pré-processeur remplacera toutes les occurrences de "abc" par "3". le compilateur ne saura même pas que "abc" à été utilisé, il n'y verra que "3" à la place.
Parce que si on ne retrouve pas la valeur binaire correspondant à 3 "à un endroit quelconque" où le processeur peut y avoir accès, on pourra danser sur la tête, on pourra pisser contre le vent ou dansser la carmagnole me processeur ne pourra pas l'utiliser.
Ta vision des choses s'arrête visiblement au compilateur, mais il faut bien comprendre qu'il y a quelque chose après le compilateur. Et ce quelque chose est trois fois rien : ce n'est jamais que le système sur lequel le programme est exécuté une fois que le code a été compilé
valeur immédiateParce que si on ne retrouve pas la valeur binaire correspondant à 3 "à un endroit quelconque"
toutes les chaine "abc" seront remplacés par "3" avant la compilation, pas pendant.
Code : Sélectionner tout - Visualiser dans une fenêtre à part mov ax,3
à l'IUT on m'a aussi appris ça:
après le préprocesseur,le compilo ne verra que:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2 #define TAB {1,2,3} int tab[3]=TAB;
toute les chaines "TAB" seront remplacé par la chaine "{1,2,3}", avant la compilation
Code : Sélectionner tout - Visualiser dans une fenêtre à part int tab[3]={1,2,3};
deviendra
Code : Sélectionner tout - Visualiser dans une fenêtre à part if(x<abc)
sinon, j'ai essayé constexpr et effectivement, ça marche
Code : Sélectionner tout - Visualiser dans une fenêtre à part if(x<3)
Mais ce que tu ne veux pas comprendre, c'est que mov ax,3 n'est encore qu'une abstraction de ce qui sera fait par le compilateur, car, en instruction processeur, nous aurons quelque chose comme
(les valeurs, à part le 3 ne sont surement pas justes. Je suis même pas sur qu'elles comportent le bon nombre de bits) où 0000110000 correspondra à l'instruction mov dans le jeu d'instructions du processeur, où 00011001 correspondra à permettra au processeur de savoir qu'il doit copier une valeur dans le registre ax et où 00000011 correspondra à la valeur qu'il doit copier dans le registre en question.
Code : Sélectionner tout - Visualiser dans une fenêtre à part 00001100000001100100000011
Donc, 3 prend de la place dans le programme, mais il se trouve aussi forcément à un endroit où il sera accessible par le processeur.
De même, si l'instruction demande de copier la valeur qui se trouve dans ax à une adresse mémoire, on retrouvera forcément la valeur 0000011 "quelque part" en mémoire.
Autrement dit, n'importe quelle valeur, qu'il s'agisse d'une constante de compilation ou non se retrouvera forcément, à un moment ou à un autre, quelque par en mémoire ou dans un registre (et parfois même à plusieurs endroits).
Il n'y a que deux exceptions (du moins à ma connaissance, s'il y en a plus, nous les compterons sur les doigts d'une main) à cet état de fait:
1- L'utilisation de l'opérateur ternaire ? avec deux valeur connues à la compilation; ex:
qui, si constante2 vaut 3 sera transformé en truc si constante1 vaut 0,1,2ou 3 et en machin si constante1 vaut plus de trois
Code : Sélectionner tout - Visualiser dans une fenêtre à part contante1 <= constante2 ? truc : machin;
2- une boucle proche dequi, le déroulement des boucles se faisant, pourrait prendre la forme de
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3 for(Type i=0; i<constante1;++1){ truc }
(enfin, pour autant que l'on soit dans une situation dans laquelle on peut effectivement dérouler la boucle)
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3 truc; truc; truc;
Et encore! 9 fois sur 10, si constante1 ou constante2 est obtenu par paramètre ou par retour de fonction, cela ne fonctionnera pas
Mais note bien que tout ce qui importe, c'est que constante1 et constante2 soient connues à la compilation.
C'est à dire que cela fonctionnera -- effectivement -- avec un #define, mais aussi
- avec la valeur de retour de sizeof()
- avec une valeur énumérée
- avec n'importe quelle constante accessible par le compilateur (par exemple cont int truc=3
- lorsque l'on utilise une valeur numérique comme paramètre template (ex: template <int NUM> struct machin{/* ... */};)
- j'oublie peut-être un ou deux cas
Note enfin qu'un code proche de const int truc=3; pourrait (il me semble du moins) tout aussi bien se traduire au final par la valeur 3 (plutôt que par la récupération de la valeur d'une variable) grâce à la suppression des variables inutiles; si toutes les expressions qui utilisent truc sont évaluables à la compilation
"abc" n'est pas une variable, c'est un morceau de chaine de caractère contenu dans le code source
le compilateur ne trouvera pas de "abc" dans le code source. il verra 3 à la place.
le compilateur ne verra que ceci:
Code : Sélectionner tout - Visualiser dans une fenêtre à part abc>4?std::cout<<"plus"<<std::endl; : std::cout<<"moins"<<endl;
mais bon, je veux bien utiliser constexpr si c'est plus moderne
Code : Sélectionner tout - Visualiser dans une fenêtre à part 3>4?std::cout<<"plus"<<std::endl; : std::cout<<"moins"<<std::endl;
Oui, je sais très bien que le compilateur verra 3 et non abc, mais ce n'est pas le problème!
Le problème, c'est que le compilateur va utiliser le code (qui a déjà été altéré par le passage du préprocesseur) pour générer un ensemble d'instructions sous une forme binaire (on est donc déjà "un cran plus loin" que l'assembleur) qui sera compréhensible par le processeur.
Or, ces instructions et les données qui permettent aux instructions processeur de travailler sont forcément mises à disposition du processeur en mémoire, sous une forme ou sous une autre.
Compiles un programme minimal qui utilise une constante de compilation (crée dans ton code de la manière que tu veux), et ouvre le avec un éditeur hexadécimal et tu te rendra compte que la valeur de ta constante se trouve dans le corps du programme.
Comme le corps du programme doit être chargé en mémoire pour que ton ordinateur puisse l'exécuter, ne vient pas me dire qu'un #define ne coute rien (je viens de citer les deux seules exceptions que je connaisse)!
Et je viens surtout pas me dire qu'il n'y a que le define qui ne coute rien dans certaines circonstances, car ce n'est pas vrai! C'est vrai pour tout ce qui peut être considéré par le compilateur comme une constante de compilation; ce qui va des valeurs énumérées à la définition de données typées et nommées, en passant par les #define et la valeur de retour de certaines fonctions (sizeof entre autres).
Et, comme il en va de même de toutes les constantes de compilation, ne vient pas me dire que le seul moyen d'obtenir une constante passe par le #define. C'est effectivement l'un des moyens que l'on a à notre disposition pour définir une constante, mais on en a de bien meilleurs dont il s'agit d'user et d'abuser pour éviter les problèmes!
Dans le cas d'un #define abc 3, le compilateur ne verra que 3, pas abc, qui sera effectivement résolu par le préprocesseur. Jusque là, personne ne dit le contraire.
Dans le cas d'un int const abc=3;, le compilateur verra des usages d'abc. Usages qu'il pourra valider par rapport au système de type en particulier. Je pense que là aussi, tout le monde est d'accord.
Là où le désaccord commence, c'est quand tu semble dire que la seconde situation implique que le programme résultant est peut-être moins performant. C'est tout simplement faux. Tout comme il y a plusieurs phases avant d'arriver au compilateur à proprement parler, il y a plusieurs phases après. L'une des phases d'optimisation a pour but de propager les valeurs constante, afin d'éviter les calculs inutiles. Lors de cette phase (où abc en tant que chaîne a déjà disparu, le compilateur sait juste qu'il s'agit de la 42ème variable déclarée dans le programme), la variable sera remplacée par 3, et cette valeur sera propagée par la suite.
Je te propose l'expérience suivante :
Va à l'adresse https://godbolt.org/g/GZSwWu j'ai entré le code suivant, et il te montre le code compilé correspondant :
Tu peux voir dans la fenêtre de droite, avec un arbre, que dans un cas, on multiplie 3 par 5, alors que dans l'autre cas, ou multiplie d2 par 5. Mais cet arbre n'est qu'un intermédiaire de la compilation, et dans le code assembleur final, tu ne vois plus aucune mention de la valeur 3. Ni venant de d1, ni de d2. Les valeurs qui apparaissent sont 15 et 12, c'est à dire le résultat des multiplications. Donc dans les 2 cas, les constantes ont été résolues pendant la compilation, et le compilateur ne s'est pas arrêté là, mais a continué à résoudre tout ce qu'il pouvait avant de générer le code final qui ne contient aucune multiplication.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12 #include <iostream> #define d1 3 int const d2 = 3; int main() { int a = d1 * 5; int b = d2 * 4; std::cout << a << b; }
Conclusion : Entre #define et constante, les constantes offrent plus de sécurité, et les deux ont les même performances. Il faut donc privilégier les constantes.
PS : Les constexpr sont avant tout un moyen de forcer encore plus de calculs pendant la compilation, et non pendant l'exécution.
PPS : Une extension intéressante du programme ci dessus est :
Si tu l'entres sur le site, tu pourra voir que même non constant, l'appel d4*8 est résolu à la compilation en 24. Par contre l'appel à d3*7 ne l'est pas, parce que le compilateur n'arrive pas à voir que la variable globale d3 n'est jamais modifiée dans le programme (alors que pour une variable locale, c'est plus simple à démontrer).
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 #include <iostream> #define d1 3 int const d2 = 3; int d3 = 3; int main() { int d4 = 3; int a = d1 * 5; int b = d2 * 4; int c = d3 * 7; int d = d4 * 8; std::cout << a << b << c << d; }
Vous avez un bloqueur de publicités installé.
Le Club Developpez.com n'affiche que des publicités IT, discrètes et non intrusives.
Afin que nous puissions continuer à vous fournir gratuitement du contenu de qualité, merci de nous soutenir en désactivant votre bloqueur de publicités sur Developpez.com.
Partager