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

GCC : probleme avec O2

10 réponses
Avatar
Nicolas BERNE
Bonjour à tous,

Je me suis remis au C et je m'amuse donc avec des petits prgs.
Le dernier en date m'a fait découvrir un truc bizarre lorsque je compile
avec l'option O2.
Je suis sous Slackware 14.0 avec gcc 4.7.1.

Voilà le source de mon prg (fibo.c) qui permet d'afficher les 1ers nbs
de Fibo :
-------------------------------------------
#include <stdio.h>
#include <stdlib.h>

#define MAX 100

int main(void)
{
long long a = 0, b = 1, tempo;
int i;

for (i = 0; i < MAX && a >= 0; i++) {
printf("%i %lld %i\n", i , a, a >= 0);

tempo = b;
b += a;
a = tempo;

/*
* en 2 instructions
* b += a;
* a = b - a;
*/

}
printf("----------\n%lld %i\n", a, a >= 0);

return EXIT_SUCCESS;
}
-------------------------------------------
Donc rien de bien compliqué.

gcc fibo.c && ./a.out donne à la fin :
...
91 4660046610375530309 1
92 7540113804746346429 1
----------
-6246583658587674878 0

Ce qui est bien ce que j'attends.

Avec les options -O ou -O1, j'obtiens la même chose.

Par contre, avec -O2, ça donne :
gcc -O2 fibo.c && ./a.out
...
91 4660046610375530309 1
92 7540113804746346429 1
93 -6246583658587674878 1
94 1293530146158671551 1
95 -4953053512429003327 1
96 -3659523366270331776 1
97 -8612576878699335103 1
98 6174643828739884737 1
99 -2437933049959450366 1
----------
3736710778780434371 1


Ca ne vient pas du type long long, car j'ai fait des essais avec des int
ou des long et j'obtiens les mêmes résultats bizarres avec l'option -O2.

Si quelqu'un pouvait éclairer ma lanterne...

A+

--
I like my coffee black
Just like my Metal

10 réponses

Avatar
Cyprien Nicolas
Le 04/08/2013 08:44, Nicolas BERNE écrivit :
Ca ne vient pas du type long long, car j'ai fait des essais avec des int
ou des long et j'obtiens les mêmes résultats bizarres avec l'option -O2.

Si quelqu'un pouvait éclairer ma lanterne...



Je pense que le compilo voit que tu ne fais que des additions sur la
variable a, donc il considère que a >= 0LL est toujours vrai et optimise
ce test.

gcc -O2 -fno-strict-overflow fibo.c et ça marche comme tu l'attends.

-fstrict-overflow permet au compilo de ne pas se poser de question sur
l'overflow, le programmeur sait ce qu'il fait, et donc il estime que ça
ne peut pas arriver. Du coup a ne peut jamais devenir négatif si cette
option est activée (parce que a est un nombre signé).
Voir le man de gcc pour plus de détails.

Si tu remplaces ta déclaration de a par:

unsigned long long a = 0;

Alors même avec -O0, ton programme va jusqu'à cent.


L'option -fwrapv peut jouer aussi.

--
« Ceci n'est pas une signature. » — René Magritte (Apocryphe)
Avatar
Nicolas BERNE
Thus Spoke Cyprien Nicolas :
Le 04/08/2013 08:44, Nicolas BERNE écrivit :
> Ca ne vient pas du type long long, car j'ai fait des essais avec des int
> ou des long et j'obtiens les mêmes résultats bizarres avec l'option -O2.
>
> Si quelqu'un pouvait éclairer ma lanterne...

Je pense que le compilo voit que tu ne fais que des additions sur la
variable a, donc il considère que a >= 0LL est toujours vrai et optimise
ce test.


Un peu rude comme optimisation...

gcc -O2 -fno-strict-overflow fibo.c et ça marche comme tu l'attends.

-fstrict-overflow permet au compilo de ne pas se poser de question sur
l'overflow, le programmeur sait ce qu'il fait, et donc il estime que ça
ne peut pas arriver. Du coup a ne peut jamais devenir négatif si cette
option est activée (parce que a est un nombre signé).
Voir le man de gcc pour plus de détails.

Si tu remplaces ta déclaration de a par:

unsigned long long a = 0;

Alors même avec -O0, ton programme va jusqu'à cent.


L'option -fwrapv peut jouer aussi.




Merci pour les explications.

A+

--
I like my coffee black
Just like my Metal
Avatar
Samuel DEVULDER
Le 04/08/2013 11:05, Nicolas BERNE a écrit :

Thus Spoke Cyprien Nicolas :
Je pense que le compilo voit que tu ne fais que des additions sur la
variable a, donc il considère que a >= 0LL est toujours vrai et optimise
ce test.





Oui c'est exactement cela.

Un peu rude comme optimisation...



Bah: je crois que la norme dit que le comportement est indéfini en cas
d'overflow sur des valeurs signées. En gros le compilo peut faire ce
qu'il veut ce sera toujours "en norme".


(...)

unsigned long long a = 0;

Alors même avec -O0, ton programme va jusqu'à cent.





La norme décrit en revanche les overflow des unsigned, et il n'y a pas
d'undefined behavior avec eux.


sam.
Avatar
Nicolas BERNE
Thus Spoke Samuel DEVULDER :
Le 04/08/2013 11:05, Nicolas BERNE a écrit :

> Thus Spoke Cyprien Nicolas :
>> Je pense que le compilo voit que tu ne fais que des additions sur la
>> variable a, donc il considère que a >= 0LL est toujours vrai et optimise
>> ce test.

Oui c'est exactement cela.

> Un peu rude comme optimisation...

Bah: je crois que la norme dit que le comportement est indéfini en cas
d'overflow sur des valeurs signées. En gros le compilo peut faire ce
qu'il veut ce sera toujours "en norme".



J'ai essayé de feinter le compilo ainsi :


int f(int x) {
return ((x+1)*(x+2));
}

dans main :
int x;

srand(time(NULL));

x = rand()%2 - 2; // x vaut donc -2 ou -1
a = f(x); // a vaut toujours 0

Et là où ça devient vraiment bizarre, si j'enlève le test a >= 0 dans la
boucle for, j'obtiens bien le résultat attendu avec -O2

92 7540113804746346429 1
93 -6246583658587674878 0
94 1293530146158671551 1
95 -4953053512429003327 0
96 -3659523366270331776 0
97 -8612576878699335103 0
98 6174643828739884737 1
99 -2437933049959450366 0

Par contre, si je remets ce test dans la boucle, ça donne :

92 7540113804746346429 1
93 -6246583658587674878 1
94 1293530146158671551 1
95 -4953053512429003327 1
96 -3659523366270331776 1
97 -8612576878699335103 1
98 6174643828739884737 1
99 -2437933049959450366 1


Bref, moi ça me dépasse.

A+

--
I like my coffee black
Just like my Metal
Avatar
Samuel DEVULDER
Le 05/08/2013 07:28, Nicolas BERNE a écrit :

Bref, moi ça me dépasse.



Regarde le code ASM généré. Je ne serais pas surpris que le compilo ait
transformé:
for (i = 0; i < MAX && a >= 0; i++) {... (a>=0) ...}

en

i = 0; if(a>=0) do {
... (a>=0) ...
} while(++i<MAX && a>=0);

Or comme ce qu'il se passe dans "..." n'influence pas la valeur de
"a>=0", le compilo effectue:

i = 0; if(a>=0) do {
... (1) ... /* d'où le printf à 1 */
} while(++i<MAX && 1);

Ca me semble très simple finalement.

sam.
Avatar
Nicolas BERNE
Thus Spoke Samuel DEVULDER :
Le 05/08/2013 07:28, Nicolas BERNE a écrit :

> Bref, moi ça me dépasse.

Regarde le code ASM généré. Je ne serais pas surpris que le compilo ait
transformé:


Heu, l'ASM et moi...

for (i = 0; i < MAX && a >= 0; i++) {... (a>=0) ...}

en

i = 0; if(a>=0) do {
... (a>=0) ...
} while(++i<MAX && a>=0);

Or comme ce qu'il se passe dans "..." n'influence pas la valeur de
"a>=0", le compilo effectue:


Justement, comment diable le compilo a pu décidé que a est toujours
positif, vu la façon dont j'initialise a à 0 (ça m'étonnerait que le
compilo ait pu analyser que, malgrè le rand, a vaut toujours 0 au départ).


i = 0; if(a>=0) do {
... (1) ... /* d'où le printf à 1 */
} while(++i<MAX && 1);

Ca me semble très simple finalement.


Mais pourquoi le compilo décide de remplacer a>=0 par 1 lorsque ce test
est dans le for (ou le while) et ne le fait pas dans le cas contraire.

D'après ce que tu as écrit, le cas où le test n'est pas dans le for
devient :
i = 0; do {
... (a>=0) ...
} while(++i<MAX);
Et là, apparemment, il ne remplace plus le test par 1...


Enfin, encore un mystère, j'ai remplacé && par & et ça donne le
résultat attendu.

Par contre, si j'initialise explicitement a à 0, que ce soit & ou &&, le
test a>=0 est toujours remplacé par 1.

A+

--
I like my coffee black
Just like my Metal
Avatar
espie
le plus simple, et portable, serait:
- de jouer avec des unsigned long long
- de garder la valeur precedente.
- de t'arreter des que ca deborde, ce qui est tres simple (suffit de verifier
que la nouvelle valeur calculee est plus petite que l'ancienne)...
Avatar
Nicolas BERNE
Thus Spoke Marc Espie :
le plus simple, et portable, serait:
- de jouer avec des unsigned long long
- de garder la valeur precedente.
- de t'arreter des que ca deborde, ce qui est tres simple (suffit de verifier
que la nouvelle valeur calculee est plus petite que l'ancienne)...



Salut,

Effectivement, comme ça, plus de soucis avec -O2.
Merci.

Voilà le source :
------------------------------------------------------
fibo-v2.c
------------------------------------------------------
#include <stdio.h>
#include <stdlib.h>

#define MAX 100

int main(void) {
unsigned long long int a = 0, b = 1, tempo;
int i = 0;

for (i = 0; i < MAX && b >= a; i++) {
printf("%i %llu %llu %in", i , a, b, b >= a);
tempo = b;
b += a;
a = tempo;
}
printf("----------n%llu %llu %in", a, b, b >= a);

return EXIT_SUCCESS;
}
------------------------------------------------------

gcc -O2 fibo-v2.c && ./a.out
...
91 4660046610375530309 7540113804746346429 1
92 7540113804746346429 12200160415121876738 1
----------
12200160415121876738 1293530146158671551 0


A+

--
I like my coffee black
Just like my Metal
Avatar
Samuel DEVULDER
Le 05/08/2013 09:51, Nicolas BERNE a écrit :

D'après ce que tu as écrit, le cas où le test n'est pas dans le for
devient :
i = 0; do {
... (a>=0) ...
} while(++i<MAX);
Et là, apparemment, il ne remplace plus le test par 1...



Parce qu'il ne peut pas inférer que a>=0. Il y a simplement trop peu
d'information (elle a disparu du test du wile). Cette inférence vient de
la simple règle que lors du bouclage du while avec a>=0 en condition, on
entre dans la boucle avec, bien entendu, a>=0 du coup il ne ré-évalue
plus cette condition et la remplace directement par 1.

sam.
Avatar
Antoine Leca
Nicolas BERNE écrivit :
Thus Spoke Cyprien Nicolas :
Le 04/08/2013 08:44, Nicolas BERNE écrivit :
Ca ne vient pas du type long long, car j'ai fait des essais avec des int
ou des long et j'obtiens les mêmes résultats bizarres avec l'option -O2.

Si quelqu'un pouvait éclairer ma lanterne...



Je pense que le compilo voit que tu ne fais que des additions sur la
variable a, donc il considère que a >= 0LL est toujours vrai et optimise
ce test.


Un peu rude comme optimisation...



Non.
Il faut que tu te rendes compte que ton code est *faux* : il utilise une
hypothèse (ici que le compilateur va utiliser un système d'entiers «qui
boucle», autrement dit l'entier suivant INT_MAX est INT_MIN) qui est
peut être intuitive pour toi, mais n'est *pas* garanti par la norme et
en fait pour laquelle il existe des implémentations (genre entiers
saturés : INT_MAX+1==INT_MAX) qui n'ont pas ce comportement.

Ce qui se passe, c'est que pour optimiser, le compilateur est parfois
amené à faire des hypothèses de nécessité sur le comportement attendu du
code ; et plus on cherche à optimiser, plus il deveinet nécessaire de
faire de telles hypothèses. Si le code respecte la norme, il n'y aura
pas de problème, les hypothèses seront valides et le code optimisé ira
plus vite ; par contre, si comme c'est le cas ici, le code enfreint les
règles, cela dérape.


Antoine