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

Evaluation d'une expression constante

31 réponses
Avatar
candide
Bonjour,

La norme dit :

6.6 Constant expressions
(...)
A constant expression can be evaluated during translation rather than
runtime,


Soit le programme suivant :

/*--------------------------------*/
#include <stdio.h>

#define A 2
#define B 5

int main(void)
{
int i;

for (i = 0; i < A * B; i++)
printf("toto\n");

return 0;
}
/*--------------------------------*/

Ai-je une quelconque garantie que l'expression A*B ne sera pas réévaluée
à chaque tour de boucle pendant l'exécution ?

Merci

10 réponses

1 2 3 4
Avatar
Michel Decima
Vincent Lefevre a écrit :
Dans l'article <491cb231$0$8219$,
Mickaël Wolff écrit:

Vincent Lefevre a écrit :



On peut très bien détecter le signe du résultat sans effectuer le
produit!





En mathématique peut-être, mais en informatique pas forcément.
Surtout pas avec les nombres signés. Sans le calculer, c'est difficile
de savoir si (1 << 16) * 2 est positif ou négatif.



1 est un entier signé positif, donc 1 << 16 est aussi positif, et
de même que (1 << 16) * 2. Rappel: il n'y a pas de wrapping sur
les entiers signés.



Est-ce que c'est vrai pour toutes les valeurs de 16 ?

void foo()
{
int tab[ ( 1 << 30 ) * 2 ] ;
}

$ gcc -c foo.c
foo.c: In function 'foo':
foo.c:3: warning: integer overflow in expression
foo.c:3: error: size of array 'tab' is negative
Avatar
Vincent Lefevre
Dans l'article <gfjqfj$g69$,
Michel Decima écrit:

Est-ce que c'est vrai pour toutes les valeurs de 16 ?



Oui, toutes celles où l'expression est bien définie (pas d'overflow).

void foo()
{
int tab[ ( 1 << 30 ) * 2 ] ;
}



$ gcc -c foo.c
foo.c: In function 'foo':
foo.c:3: warning: integer overflow in expression


^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

foo.c:3: error: size of array 'tab' is negative



Cette erreur, c'est spécifique à l'implémentation, conséquence
de l'integer overflow. L'implémentation aurait très bien pu
choisir de donner une valeur positive à ( 1 << 30 ) * 2, auquel
cas le programme aurait pu être compilé (éventuellement sans
warning).

--
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
Michel Decima
Vincent Lefevre a écrit :
Dans l'article <gfjqfj$g69$,
Michel Decima écrit:

Est-ce que c'est vrai pour toutes les valeurs de 16 ?



Oui, toutes celles où l'expression est bien définie (pas d'overflow).



C'est bien ce que je voulais entendre, et cette notion d'overflow
n'apparaissait pas dans ta justification de (1<<16)*2 est positif.

Je reformule la remarque de Mickael en remplacant le verbe "calculer":

"Sans verifier que l'expression est bien definie sur la plateforme cible
(pas d'overflow), il est difficile de savoir si (1<<16)*2 est positif
ou negatif."


void foo()
{
int tab[ ( 1 << 30 ) * 2 ] ;
}



$ gcc -c foo.c
foo.c: In function 'foo':
foo.c:3: warning: integer overflow in expression


^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

foo.c:3: error: size of array 'tab' is negative



Cette erreur, c'est spécifique à l'implémentation, conséquence
de l'integer overflow. L'implémentation aurait très bien pu
choisir de donner une valeur positive à ( 1 << 30 ) * 2, auquel
cas le programme aurait pu être compilé (éventuellement sans
warning).



Oui. Connaissant l'implementation, j'ai choisi expres une grande valeur
de 16 pour provoquer l'apparition d'une valeur negative.

Je comprends bien que ce comportement est specifique a l'implementation,
mais j'observe la meme chose sur toutes les plateformes auquel j'ai
acces (linux/x86, hpux/ia64, aix/power). Evidemment, ca ne prouve rien
pour les autres ;)
Avatar
Antoine Leca
En news:20081117012443$, Vincent Lefevre va
escriure:
Dans l'article <gfjqfj$g69$,
Michel Decima écrit:

Est-ce que c'est vrai pour toutes les valeurs de 16 ?



Oui, toutes celles où l'expression est bien définie (pas d'overflow).


<zap>
foo.c:3: warning: integer overflow in expression


^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
foo.c:3: error: size of array 'tab' is negative



Cette erreur, c'est spécifique à l'implémentation, conséquence
de l'integer overflow.



Certes. Mais toute implémentation C aura une ou plusieurs limites (peu
importent les valeurs d'icelles).
Donc le compilateur sera bien obligé de faire l'évaluation A*B, justement
pour le cas où est franchie la dite limite de l'implémentation.

(Sans compter qu'il est plus facile de faire le calcul A*B que d'avoir à
gérer une table à je-ne-sais combien d'entrées, en fonction des types
respectifs de A et B, pour déterminer si le résultat est négatif ou nul.)


Antoine
Avatar
Antoine Leca
En news:20081114092411$, Vincent Lefevre va
escriure:
Dans l'article <491cb231$0$8219$,
Mickaël Wolff écrit:

Vincent Lefevre a écrit :



On peut très bien détecter le signe du résultat sans effectuer le
produit!





En mathématique peut-être, mais en informatique pas forcément.
Surtout pas avec les nombres signés. Sans le calculer, c'est
difficile de savoir si (1 << 16) * 2 est positif ou négatif.



1 est un entier signé positif,



Oui

donc 1 << 16 est aussi positif, et de même que (1 << 16) * 2.




Pas forcément: cf. C99 6.1.3.1p3.


Rappel: il n'y a pas de wrapping sur les entiers signés.



Sur ta machine peut-être (cela fait partie des choses définies par
l'implémentation, et je sais qu'il existe des machines à arithmétique
entière avec saturation, surtout en QN), mais ce n'est pas commun.


Mais bon, il suffit que le signe soit détectable dans certains
cas pour que dans ces cas-là, le compilateur ne soit pas obligé
d'effectuer le calcul à la compilation.



Même si l'on effectue pas le calcul, cela reste une évaluation (au sens le
plus commun du terme).


Antoine
Avatar
Vincent Lefevre
Dans l'article <gfs377$pc5$,
Antoine Leca écrit:

En news:20081114092411$, Vincent Lefevre va
escriure:



> donc 1 << 16 est aussi positif, et de même que (1 << 16) * 2.



Pas forcément: cf. C99 6.1.3.1p3.



N'existe pas ici.

> Rappel: il n'y a pas de wrapping sur les entiers signés.



Sur ta machine peut-être (cela fait partie des choses définies par
l'implémentation, et je sais qu'il existe des machines à arithmétique
entière avec saturation, surtout en QN),



J'entendais par là: il n'y a pas de wrapping garanti.

mais ce n'est pas commun.



Avec gcc, il faut -fwrapv pour qu'on soit sûr d'obtenir l'effet souhaité.

> Mais bon, il suffit que le signe soit détectable dans certains
> cas pour que dans ces cas-là, le compilateur ne soit pas obligé
> d'effectuer le calcul à la compilation.



Même si l'on effectue pas le calcul, cela reste une évaluation (au
sens le plus commun du terme).



Le compilateur se comporte peut-être comme si c'était évalué (ça reste
bien flou), mais cela ne veut pas dire qu'il codera en dur le résultat
de l'évaluation.

--
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 <gfrcqf$oht$,
Michel Decima écrit:

Je reformule la remarque de Mickael en remplacant le verbe "calculer":



"Sans verifier que l'expression est bien definie sur la plateforme cible
(pas d'overflow), il est difficile de savoir si (1<<16)*2 est positif
ou negatif."



Pas forcément. Ça dépend de la tête de l'expression. Par exemple, si
a, b et c sont positifs et qu'il n'y a pas d'overflow ni comportement
indéfini, alors (a << b) * c est positif. Un compilateur pourrait
très bien avoir ce genre de règles (e.g. s'il est conçu pour des
applications particulières...).

Je comprends bien que ce comportement est specifique a l'implementation,
mais j'observe la meme chose sur toutes les plateformes auquel j'ai
acces (linux/x86, hpux/ia64, aix/power). Evidemment, ca ne prouve rien
pour les autres ;)



Je suis d'accord que ça reste théorique, mais on a parfois des timings
surprenants.

--
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 <gfs285$jgq$,
Antoine Leca écrit:

Donc le compilateur sera bien obligé de faire l'évaluation A*B, justement
pour le cas où est franchie la dite limite de l'implémentation.



Pas obligé, même si c'est ce qui est le plus probable.

(Sans compter qu'il est plus facile de faire le calcul A*B que d'avoir à
gérer une table à je-ne-sais combien d'entrées, en fonction des types
respectifs de A et B, pour déterminer si le résultat est négatif ou nul.)



Pas forcément (e.g. avec une application particulière et un compilateur
associé, où A et B ont des formes particulières).

--
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:20081117175312$, Vincent Lefevre va
escriure:

donc 1 << 16 est aussi positif, et de même que (1 << 16) * 2.
Pas forcément: cf. C99 6.1.3.1p3.



N'existe pas ici.



Exact, il fallait lire 6.3.1.3p3.


Le compilateur se comporte peut-être comme si c'était évalué (ça reste
bien flou), mais cela ne veut pas dire qu'il codera en dur le résultat
de l'évaluation.



Mais je n'ai jamais dit ni même sous-entendu qu'il n'était pas possible de
(re)faire l'évaluation au moment de l'exécution.
Voire même comme le proposait Marc, de faire trois évaluations distinctes et
de choisir la plus « sûre ».


Ton propos initial était de refuser l'argument de l'article
<gf9s85$rbc$, où Charlie Gordon
écrit:

: - le compilateur est obligé de savoir faire l'évaluation des expressions
: constantes au moment de la compilation, sinon impossible de déclarer
``char
: a[A * B]''.

Moi je dis qu'il est effectivement bien obligé, histoire d'attraper un truc
comme

#include<limits.h>
#define A 2
#define B (INT_MAX+1u)
char a[A*B];


(et oui, je sais qu'en théorie C99 il y a des machines où INT_MAX+1u n'est
PAS la moitié de UINT_MAX+1, dans ce cas merci de remplacer A dans
l'expression ci-dessus par la puissance de 2 appropriée.)


Antoine
Avatar
Vincent Lefevre
Dans l'article <gg6svg$rle$,
Antoine Leca écrit:

En news:20081117175312$, Vincent Lefevre va
escriure:



>> donc 1 << 16 est aussi positif, et de même que (1 << 16) * 2.
>> Pas forcément: cf. C99 6.1.3.1p3.
>
> N'existe pas ici.



Exact, il fallait lire 6.3.1.3p3.



Attends, ceci concerne les *conversions*. Ici, pas de conversion. Les
règles pour les opérations, style * (multiplication), sont différentes.
Cf 6.5#5: c'est de l'undefined behavior.

Ton propos initial était de refuser l'argument de l'article
<gf9s85$rbc$, où Charlie Gordon
écrit:



: - le compilateur est obligé de savoir faire l'évaluation des expressions
: constantes au moment de la compilation, sinon impossible de déclarer
``char
: a[A * B]''.



Moi je dis qu'il est effectivement bien obligé, histoire d'attraper un truc
comme



#include<limits.h>
#define A 2
#define B (INT_MAX+1u)
char a[A*B];



Un exemple n'a jamais rien prouvé sur le cas général. Le point
important est qu'il *existe* des expressions constantes que le
compilateur n'a pas besoin d'évaluer complètement (i.e. d'en
connaître le résultat) pour qu'il se comporte de manière conforme
à la norme. De telles expressions constantes peuvent alors très
bien être évaluées à l'exécution.

Par exemple, pour A*B sur du non signé, on peut raisonner sur la
valuation 2-adique.

--
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)
1 2 3 4