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

post/pre incrementations et bugs de compilos

19 réponses
Avatar
Jylam
Salut a tous,

On m'a posé devant un "bug" que je n'ai pas su expliquer.

Alors, le code :



8<---------- snip

#include <stdio.h>

void f(int a, int b, int c) { printf("%i %i %i", a, b, c); }

int main(int argc, char *argv[])
{
int i = 3;
f(i++, --i, ++i);
return 0;
}

8<---------- snip

(rendu valide pour eviter les remarques ;)

Voila. Le probleme en lui meme : Voila ce que me sortent les differents
programmes compilés, suivant les compilos utilisés :

Gcc 3.3.5 x86 linux sans optim (-o0) : 3 3 4
Gcc 3.3.5 x86 linux avec optim (-o6) : 3 4 4
Visual C++ 6 Debug : 3 3 4
Visual C++ 6 Release : 3 4 4
Forte7 UltraSPARC III Solaris sans optim : 3 4 4
Forte7 UltraSPARC III Solaris avec optim : 3 4 4
Gcc 2.95 UltraSPARC III Solaris sans optim : 3 4 4
Gcc 2.95 UltraSPARC III Solaris avec optim : 3 4 4


Le resultat est sensé etre 3 3 4 (arguments de droite a gauche), selon
moi. Probleme, non seulement tous les compilos ne donnent pas ca, mais
en plus un meme compilo, suivant ses optims, peut donner un resultat
different pour un code similaire.

(Le passage a une fonction n'est pas obligatoire, afficher un printf("%d
%d %d\n", i++, --i, ++i); donne le meme bug il me semble)


Si quelqu'un avait une explication a ca, je suis preneur :)


Merci bieng.

--
Jean-Yves Lamoureux

10 réponses

1 2
Avatar
Emmanuel Delahaye
Jylam wrote on 08/09/05 :
#include <stdio.h>

void f(int a, int b, int c) { printf("%i %i %i", a, b, c); }

int main(int argc, char *argv[])
{
int i = 3;
f(i++, --i, ++i);
return 0;
}


Le comportement dépend du compilateur. Ce genre d'expression est
déconseillée car non portable.

--
Emmanuel
The C-FAQ: http://www.eskimo.com/~scs/C-faq/faq.html
The C-library: http://www.dinkumware.com/refxc.html

"It's specified. But anyone who writes code like that should be
transmogrified into earthworms and fed to ducks." -- Chris Dollin CLC

Avatar
Richard Delorme
#include <stdio.h>

void f(int a, int b, int c) { printf("%i %i %i", a, b, c); }

int main(int argc, char *argv[])
{
int i = 3;
f(i++, --i, ++i);
return 0;
}

Le resultat est sensé etre 3 3 4 (arguments de droite a gauche),


Non, l'ordre d'évaluation des arguments n'est pas spécifié par la norme,
tout est possible.

Si quelqu'un avait une explication a ca, je suis preneur :)


Le chapitre 6.5.2.2 verset 10 :

The order of evaluation of the function designator, the actual
arguments, and subexpressions within the actual arguments is
unspecified, but there is a sequence point before the actual call.

--
Richard

Avatar
none
Jylam wrote:

Salut a tous,

On m'a posé devant un "bug" que je n'ai pas su expliquer.

Alors, le code :



8<---------- snip

#include <stdio.h>

void f(int a, int b, int c) { printf("%i %i %i", a, b, c); }

int main(int argc, char *argv[])
{
int i = 3;
f(i++, --i, ++i);
return 0;
}

8<---------- snip

(rendu valide pour eviter les remarques ;)

Voila. Le probleme en lui meme : Voila ce que me sortent les differents
programmes compilés, suivant les compilos utilisés :

Gcc 3.3.5 x86 linux sans optim (-o0) : 3 3 4
Gcc 3.3.5 x86 linux avec optim (-o6) : 3 4 4
Visual C++ 6 Debug : 3 3 4
Visual C++ 6 Release : 3 4 4
Forte7 UltraSPARC III Solaris sans optim : 3 4 4
Forte7 UltraSPARC III Solaris avec optim : 3 4 4
Gcc 2.95 UltraSPARC III Solaris sans optim : 3 4 4
Gcc 2.95 UltraSPARC III Solaris avec optim : 3 4 4


Le resultat est sensé etre 3 3 4 (arguments de droite a gauche), selon
moi. Probleme, non seulement tous les compilos ne donnent pas ca, mais
en plus un meme compilo, suivant ses optims, peut donner un resultat
different pour un code similaire.

(Le passage a une fonction n'est pas obligatoire, afficher un printf("%d
%d %dn", i++, --i, ++i); donne le meme bug il me semble)


Si quelqu'un avait une explication a ca, je suis preneur :)


Merci bieng.

--
Jean-Yves Lamoureux


L'ordre d'évaluation des arguments n'est pas définit dans la norme.
Pour ce cas, l'évaluation du paramètre b et c peut changer.
Les valeurs attenduent sont 2 3 ou 3 4.
Les compilateurs affichant d'autres résulat sont simplement bogués.
Ce genre de codage est plus un piège à compilateur....

Pascal

Avatar
Antoine Leca
En <news:dfrnh7$j63$, Pascal va escriure:
Jylam wrote:
int i = 3;
f(i++, --i, ++i);


L'ordre d'évaluation des arguments n'est pas définit dans la norme.
Pour ce cas, l'évaluation du paramètre b et c peut changer.


Oui. Les autres aussi.


Les valeurs attenduent sont 2 3 ou 3 4.


Je ne sais pas comment tu obtiens cette idée.

Il y a sept opérations :

a) i += 1
b) a = i
c) i -= 1
d) b = i
e) i += 1
f) c = i
g) appelle f

a, b et c sont les arguments de la fonction. L'ordre d'évaluation est
seulement partiellement défini :
(opérateur post ++) : a) suit b)
(opérateur pré --) : d) suit c)
(opérateur pré ++) : f) suit e)
(appel de fonction) : g) suit b), d) et f)
(point de séquence avant l'appel) : g) suit a), c) et e)

Cela laisse pas mal de possiblités « inattendues » ouvertes : par exemple,
l'ordre relatif de a), c) et e). Quelques exemples, sans tenir compte de b)
qui intervient toujours avant a) :

Ordre 1: c d e f a
i contient: 3 2 2 3 3 4 b=2 c=3

Ordre 2: c e d f a
i contient: 3 2 3 3 3 4 b=3 c=3

Ordre 3: e f c d a
i contient: 3 4 4 3 3 4 b=3 c=4


Ordre 4: c d a e f
i contient: 3 2 2 3 4 4 b=2 c=4

Ordre 5: a c e d f
i contient: 3 4 3 4 4 4 b=4 c=4

Ordre 6: a e f c d
i contient: 3 4 5 5 4 4 b=4 c=5



Les compilateurs affichant d'autres résulat sont simplement bogués.


Ah ? Tu peux m'expliquer en quoi les compilateurs cités par Jean-Yves qui
sortent 4 4 (en gros, tous les compilateurs optimisateurs) sont bogués ?

Je veux bien comprendre que l'on rajoute une contrainte b) avant d) avant f)
(pour respecter l'ordre de passage des paramètres), ou son dual f) avant d)
avant b), plus probable car plus conforme à l'ordre d'empilage des
paramètres dans la convention d'appel la plus répandue; ceci interdit mes
« solutions » 4 et 6; pour le reste...


Ce genre de codage est plus un piège à compilateur....


Non, cela arrive très (trop) souvent dans la pratique, et il est important
de l'éviter. Mais pour cela, il faut comprendre ce qui se passe.


Antoine


Avatar
Hamiral
Je veux bien comprendre que l'on rajoute une contrainte b) avant d) avant f)
(pour respecter l'ordre de passage des paramètres)


Justement, le "bug" ne pourrait-il pas venir de là ? Selon les options
d'optimisation, le processeur etc, les paramètres peuvent être passés de
manières bien différentes non ? Genre sur la pile, ou certains sur la
pile, d'autres sur les registres ...

Enfin c'est vraiment la seule piste qui me vient à l'esprit, surtout que
Jean-Yves a bien attiré notre attention sur le fait qu'un même
compilateur, avec des options de compilation différentes, donne des
résultats différents ...



--
Hamiral

Avatar
Pierre Maurette
Je veux bien comprendre que l'on rajoute une contrainte b) avant d) avant f)
(pour respecter l'ordre de passage des paramètres)


Justement, le "bug" ne pourrait-il pas venir de là ? Selon les options
Il n'y a pas de "bug", même avec des guillemets. La norme est claire,

l'ordre d'évaluation est "unspecied". La seule certitude, c'est que i
vaudra 4 au retour de la fonction (en fait, juste avant le saut vers la
fonction).
Il n'y a pas non plus de bug chez Microsoft (pas vérifié pour les
autres), la documentation est claire également: elle rappelle la norme,
et surtout précise que l'ordre d'évaluation peut varier également avec
les *niveaux d'optimisation*, notion inconnue de la norme. S'y ajoute
des considérations sur les effets de bord et le conseil d'éviter de se
mettre en situation de les subir.

d'optimisation, le processeur etc, les paramètres peuvent être passés de
manières bien différentes non ? Genre sur la pile, ou certains sur la
pile, d'autres sur les registres ...
Je pense qu'il n'y a qu'une seule alternative: soit respecter les

conventions d'appel, soit développer la fonction inline. Même dans ce
dernier cas, et même si la fonction n'est jamais appelée, elle sera
traduite (mais pas nécessairement liée, à mon avis).
Pour autoriser l'inlining à la discrétion du compilateur sous VC++,
c'est /Ob2.

Enfin c'est vraiment la seule piste qui me vient à l'esprit, surtout que
Jean-Yves a bien attiré notre attention sur le fait qu'un même
compilateur, avec des options de compilation différentes, donne des
résultats différents ...
Ce ne peut être une explication. En effet, la première optimisation qui

sera faite dans le cadre de l'exemple proposé sera de remplacer les
arguments par des constantes calculées au compil-time. Et là, le
compilateur est totalement libre de l'algo de calcul des arguments. Les
résultats en release sont d'ailleurs étonnants:
VC6:
; 8 : int i = 3;
; 9 : f(i++, --i, ++i);

push 4
push 4
push 3
call _f


VC6 /Ob2:
; 8 : int i = 3;
; 9 : f(i++, --i, ++i);

push 4
push 3
push 3
push OFFSET FLAT:??@?$CFi?5?$CFi?5?$CFi?6?$AA@ ; `string'
call _printf


VC7.1
; 8 : int i = 3;
; 9 : f(i++, --i, ++i);

push 3
push 3
push 3
push OFFSET FLAT:??@?$CFi?5?$CFi?5?$CFi?6?$AA@
call _printf


--
Pour répondre directement: enlever une lettre sur deux
wwaannaaddoooo -> wanadoo

Pierre Maurette


Avatar
Emmanuel Delahaye
Pierre Maurette wrote on 10/09/05 :
Les résultats en release sont
d'ailleurs étonnants:
VC6:
; 8 : int i = 3;
; 9 : f(i++, --i, ++i);

push 4
push 4
push 3
call _f

VC6 /Ob2:
push 4
push 3
push 3
push OFFSET FLAT:??@?$CFi?5?$CFi?5?$CFi?6?$AA@ ; `string'
call _printf

VC7.1
push 3
push 3
push 3
push OFFSET FLAT:??@?$CFi?5?$CFi?5?$CFi?6?$AA@
call _printf


Trop marrant !

--
Emmanuel
The C-FAQ: http://www.eskimo.com/~scs/C-faq/faq.html
The C-library: http://www.dinkumware.com/refxc.html

"C is a sharp tool"

Avatar
Charlie Gordon
"Pierre Maurette" wrote in
message news:
Je veux bien comprendre que l'on rajoute une contrainte b) avant d) avant
f)



(pour respecter l'ordre de passage des paramètres)


Justement, le "bug" ne pourrait-il pas venir de là ? Selon les options
Il n'y a pas de "bug", même avec des guillemets. La norme est claire,

l'ordre d'évaluation est "unspecied". La seule certitude, c'est que i
vaudra 4 au retour de la fonction (en fait, juste avant le saut vers la
fonction).


En fait je pense que cette certitude n'est pas du tout fondée.
D'après la norme, lire plusieurs fois la valeur d'un variable modifiee par effet
de bord entre deux points de séquence est un cas de unspecified behaviour, a
fortiori la modifier par effet de bord plusieurs fois !

Bref ce truc est interdit.

Chqrlie.

PS: il semblerait que Microsoft ait décider de spécifier un comportement
systématique pour ce type de problème dans C#...



Avatar
Jean-Marc Bourguet
"Charlie Gordon" writes:

Il n'y a pas de "bug", même avec des guillemets. La norme est
claire, l'ordre d'évaluation est "unspecied". La seule certitude,
c'est que i vaudra 4 au retour de la fonction (en fait, juste
avant le saut vers la fonction).


En fait je pense que cette certitude n'est pas du tout fondée.
D'après la norme, lire plusieurs fois la valeur d'un variable
modifiee par effet de bord entre deux points de séquence est un cas
de unspecified behaviour, a fortiori la modifier par effet de bord
plusieurs fois !

Bref ce truc est interdit.


Petit rappel:

comportement non defini (undefined behavior): la norme ne dit rien,
on n'est meme pas sur d'avoir un comportement coherent d'un
passage a l'autre dans la meme fonction.
comportement non specifie (unspecified behavior): la norme demande
que le comportement de chaque instance soit choisi parmi une serie
de comportement possible
comportement defini par l'implementation (implementation-defined
behavior): c'est un comportement non specifie avec en plus une
obligation de documentation de la methode de choix.

L'ordre d'evaluation des parametres est du comportement non specifie
(c'est meme l'example donne dans la norme lors de la definition du
terme si j'ai bonne memoire). Modifier plusieurs fois une variable
entre deux points de sequencement est du comportment non defini.

Autrement dit
int f(int a, int b) { return a * b; }
i = 0;
k = f(i++, i++);
on sait qu'apres l'execution k vaut 0 et i 2 mais on ne sait pas si
l'appel c'est fait avec a=0 et b=1 ou bien a=1 et b=0. Alors qu'avec
i = 0;
k = i++ * i++;
on ne peut rien dire du tout, ni sur la valeur de k ni celle de i.

A+
--
Jean-Marc
FAQ de fclc: http://www.isty-info.uvsq.fr/~rumeau/fclc
Site de usenet-fr: http://www.usenet-fr.news.eu.org


Avatar
Antoine Leca
En <news:, Pierre Maurette va
escriure:
Je veux bien comprendre que l'on rajoute une contrainte b) avant d)
avant f) (pour respecter l'ordre de passage des paramètres)


Justement, le "bug" ne pourrait-il pas venir de là ? Selon les
options
Il n'y a pas de "bug", même avec des guillemets.



Dépend de ton concept de bogue. Certains clients attendent certains
comportements (en particulier, que l'ordre d'évaluation ne change pas avec
le niveau d'optimisation); et ce sont les clients qui payent, donc parfois
ils auront plus raison que la norme.

La norme est claire, l'ordre d'évaluation est "unspecied". La seule
certitude, c'est que i vaudra 4 au retour de la fonction (en fait,
juste avant le saut vers la fonction).


C'est une certitude selon la norme.
Mais un compilateur n'est pas forcément complètement conforme à la norme.
Parfois cette différence est une erreur des programmeurs, c'est donc un
bogue. Parfois, c'est voulu (et les programmes doivent faire avec).
En l'occurence, la norme est claire, ce genre de programme ne doit pas être
utilisé si on cherche la portabilité, donc le comportement réellement
observable n'est pas le problème de la norme (et oui, je mélange à propos le
fait que l'ordre d'évaluation n'est pas spécifié avec le fait que les
opérations de pré et post incrémentation mélangées comme elles le sont, ont
un comportement indéfini).



Je pense qu'il n'y a qu'une seule alternative: soit respecter les
conventions d'appel, soit développer la fonction inline.


Non. Les conventions d'appel ne sont pas forcément uniques, tu peux (et
généralement tu as, pour des raisons de marché et de différenciations
marketing) des extensions ou des options (#pragma) permettant de les
modifier.
De plus, si les conventions de passage sont par pile, en général cela va
créer une contrainte d'ordre partiel. Mais si les conventions utilisent les
registres, cette contrainte disparaît ; et les compilateurs actuels vont
magouiller l'expression pour espacer le plus possible les interactions avec
chaque espace de mémoire (y compris les registres), histoire de remplir tous
les pipelines parallèles.


Antoine



1 2