Operations binaires

Le
Mickael Pointier
Salut.

J'ai un de mes collègues qui est tombé sur ce qui ressemble à un bug de
Visual.net (non présent dans Visual 6), mais bon, c'est peut-être bettement
que ce qu'il a fait à un comportement indéfini, donc je préfère demander ici
avant éventuellement de reporter le bug à qui de droit.

Voila son mail original:


==
Bonjour,
.NET a un gros problème avec les décalages de valeurs signées en release.
En debug le code est nickel, mais en release il compile n'importe quoi.
C'est un PB que j'ai eu sur edAudio Hier, ((nValue>>31)^nValue) donne une
valeur approchée de abs(nValue), mais sans appel de fonctions et sans test.
J'utilisait ceci pour rapidement évaluer le volume d'un échantillon.
Le problème existe sur .NET 2002 tout comme sur .NET 2003.
J'ai pas testé sur Visual studio 6.
Moralité, si votre code marche bien en debug mais fait n'importe quoi en
release, vérifiez vos décalages binaires de valeurs signées.
David.

Testez par vous même:

#include <stdio.h>
#include <conio.h>
void main()
{
int nValue;
printf("entrey la valeur -1024 et comparez le resultat entre debug et
release");
scanf("%d",&nValue);
int nResult = ((nValue>>31)^nValue)>>4;
printf("Resultat %d",nResult);
}
==

[C'est du C, mais c'est compilé en tant que C++]

En debug, le code généré est "correct", c'est à dire qu'il fait bien ce que
j'espérait qu'il fasse :)
-1024 = 0xFFFFFC00
Décallé de 31 à droite nous fait une extension de signe, donc = 0xFFFFFFFF
En faisant un "ou exclusif" avec la valeur d'origine on obtient 0x3FF, c'est
à dire +1023
Et le décallage final de 4 vers la droite nous donne +63.

En release par contre, il ne fait plus du tout la même chose, en fait il ne
reste plus que le décallage de 4 à droite:
-1024 = 0xFFFFFC00
Décallage final de 4 qui donne 0xFFFFFFC0

Si j'enlève le >>4 du code, donc ne garde que ceci:

int nResult = (nValue>>31)^nValue;

J'obtient bien +1023 aussi bien en mode Debug qu'en mode Release.

Si j'écrit l'expression sur deux lignes:

int nResult = (nValue>>31)^nValue;
nResult>>=4;

Le problème reste présent en Release, le décallage et le xor ne sont pas
apparent dans le code.

Si j'écrit ca:

int nValue;
scanf("%d",&nValue);
int nResult = (nValue>>31)^nValue;
printf("Resultat %d",nResult);
nResult>>=4;
printf("Resultat %d",nResult);

J'obtient bien un premier affichage avec +1023 et un secon affichage avec
+63.

Alors, bug, ou bien résultat d'un comportement indéfini ???

Merci d'avance !

Mike
Vos réponses
Gagnez chaque mois un abonnement Premium avec GNT : Inscrivez-vous !
Trier par : date / pertinence
Loïc Joly
Le #722378
Mickael Pointier wrote:

Salut.

J'ai un de mes collègues qui est tombé sur ce qui ressemble à un bug de
Visual.net (non présent dans Visual 6), mais bon, c'est peut-être bettement
que ce qu'il a fait à un comportement indéfini, donc je préfère demander ici
avant éventuellement de reporter le bug à qui de droit.

Voila son mail original:


========================= > Bonjour,
.NET a un gros problème avec les décalages de valeurs signées en release.
En debug le code est nickel, mais en release il compile n'importe quoi.
C'est un PB que j'ai eu sur edAudio Hier, ((nValue>>31)^nValue) donne une
valeur approchée de abs(nValue), mais sans appel de fonctions et sans test.


Personnellement, je préfère abs(nValue), et je ne suis même pas certain
des performances. J'ai donc fait quelques tests :

Dev C++/gcc :
Abs : 0.719
Truc sur les bits : 0.328
Visual C++ :
Abs : 0.312
Truc sur les bits : 0.297

Sur visual C++ (qui semble l'environnement cible), la différence
vaut-elle le coup (surtout que abs donne une valeur correcte) ?

D'autant plus que la norme n'indique rien sur la valeur obtenue :

The value of E1 >> E2 is E1 rightshifted E2 bit positions. If E1 has an
unsigned type or if E1 has a signed type and a nonnegative value, the
value of the result is the integral part of the quotient of E1 divided
by the quantity 2 raised to the power E2. If E1 has a signed type and a
negative value, the resulting value is implementationdefined.

Mais il doit y avoir une seule valeur obtenue. Qu'elle soit différente
en debug et en release me semble surprenant, mauvais en terme de QoI,
mais correct par rapport à la norme (debug et release sont deux
implémentations différentes...). Qu'elle soit différente entre :

int nResult = (nValue>>31)^nValue;
nResult>>=4;

Et :

int nResult = ((nValue>>31)^nValue);
printf("Resultat %dn",nResult);
nResult>>=4;

Me semble incorrect.

--
Loïc

Pierre Maurette
Le #722377
"Mickael Pointier"
Salut.
[...]


Alors, bug, ou bien résultat d'un comportement indéfini ???
Je pencherais pour un bug de l'optimiseur, mais avec la prudence

nécessaire dans ce genre de situation "c'est pas moi, c'est Bill".
Voici mes constatations:

- Le résultat en release est logiquement 63 avec Borland C++5.6 et g++
3.2, et j'ai bien -64 avec VC++7.1 (hors de l'EDI VS .NET).

- "Tout se passe comme si" VC++ optimisait (A>>31)^A en A (donc
peut-être, c'est ce que semble montrer le code asm) A>>31 en 0.

- J'ai testé:

<code>
#include
int main(void)
{
int nResult;
int nValue1;
const int nValue2 = -1024;
static const int nValue3 = -1024;
nValue1 = -1024;
nResult = ((nValue1>>31)^nValue1)>>4;
printf("Resultat %dn",nResult);
nResult = ((nValue2>>31)^nValue2)>>4;
printf("Resultat %dn",nResult);
nResult = ((nValue3>>31)^nValue3)>>4;
printf("Resultat %dn",nResult);
return 0;
}
</code>

(J'ai regardé le code machine pour voir ce qui est évalué au
compil-time et ce qui calculé au run-time)

Le fichier est nommé test.cpp :
Le résultat en release est :
Resultat -64
Resultat 63
Resultat 63
nValue1 est évaluée incorrectement (par l'"optimiseur" ?).
nValue2 est évaluée correctement par le compilateur.
nValue3 est évaluée correctement par le compilateur.
Le résultat en debug est logiquement :
Resultat 63
Resultat 63
Resultat 63
nValue1 est calculée correctement au run-time par le code généré.
nValue2 est évaluée correctement par le compilateur.
nValue3 est évaluée correctement par le compilateur.

Le fichier est nommé test.c :
Le résultat en release est :
Resultat -64
Resultat -64
Resultat 63
nValue1 est évaluée incorrectement (par l'"optimiseur" ?).
nValue2 est évaluée incorrectement (par l'"optimiseur" ?).
nValue3 est évaluée correctement par le compilateur.
Le résultat en debug est logiquement :
Resultat 63
Resultat 63
Resultat 63
nValue1 est calculée correctement au run-time par le code généré.
nValue2 est calculée correctement au run-time par le code généré.
nValue3 est calculée correctement au run-time par le code généré.

Alors? Ça sent le bug.

--
Pierre

Pierre Maurette
Le #722071
Loïc Joly
Mickael Pointier wrote:

Salut.

J'ai un de mes collègues qui est tombé sur ce qui ressemble à un bug de
Visual.net (non présent dans Visual 6), mais bon, c'est peut-être bettement
que ce qu'il a fait à un comportement indéfini, donc je préfère demander ici
avant éventuellement de reporter le bug à qui de droit.

Voila son mail original:


========================= >> Bonjour,
.NET a un gros problème avec les décalages de valeurs signées en release.
En debug le code est nickel, mais en release il compile n'importe quoi.
C'est un PB que j'ai eu sur edAudio Hier, ((nValue>>31)^nValue) donne une
valeur approchée de abs(nValue), mais sans appel de fonctions et sans test.


Personnellement, je préfère abs(nValue), et je ne suis même pas certain
des performances. J'ai donc fait quelques tests :

Dev C++/gcc :
Abs : 0.719
Truc sur les bits : 0.328
Visual C++ :
Abs : 0.312
Truc sur les bits : 0.297

Sur visual C++ (qui semble l'environnement cible), la différence
vaut-elle le coup (surtout que abs donne une valeur correcte) ?

D'autant plus que la norme n'indique rien sur la valeur obtenue :

The value of E1 >> E2 is E1 rightshifted E2 bit positions. If E1 has an
unsigned type or if E1 has a signed type and a nonnegative value, the
value of the result is the integral part of the quotient of E1 divided
by the quantity 2 raised to the power E2. If E1 has a signed type and a
negative value, the resulting value is implementationdefined.

Ne pas confondre bug et non-conformité à la norme. Le bug est

pratiquement un "délit commercial", à juger par rapport à un contrat.
La norme dit "implementation defined", l'implémentation documente :


The right shift operator causes the bit pattern in the first operand
to be shifted right the number of bits specified by the second
operand. Bits vacated by the shift operation are zero-filled for
unsigned quantities. For signed quantities, the sign bit is propagated
into the vacated bit positions. The shift is a logical shift if the
left operand is an unsigned quantity; otherwise, it is an arithmetic
shift.

Microsoft Specific

The result of a right shift of a signed negative quantity is
implementation dependent. Although Microsoft C++ propagates the
most-significant bit to fill vacated bit positions, there is no
guarantee that other implementations will do likewise.

END Microsoft Specific

Le "contrat" est clair. J'intuite qu'il est le même pour toutes les
implémentations "complément à 2".

Mais il doit y avoir une seule valeur obtenue. Qu'elle soit différente
en debug et en release me semble surprenant, mauvais en terme de QoI,
mais correct par rapport à la norme (debug et release sont deux
implémentations différentes...). Qu'elle soit différente entre :

int nResult = (nValue>>31)^nValue;
nResult>>=4;

Et :

int nResult = ((nValue>>31)^nValue);
printf("Resultat %dn",nResult);
nResult>>=4;

Me semble incorrect.
Mais éventuellement explicable par un bug dans l'optimiseur sur une

simplification de ((X>>31)^X)>>4.
--
Pierre


K. Ahausse
Le #722068
Testez par vous même:

#include #include void main()
{
int nValue;
printf("entrey la valeur -1024 et comparez le resultat entre debug et
releasen");
scanf("%d",&nValue);
int nResult = ((nValue>>31)^nValue)>>4;
printf("Resultat %dn",nResult);
}
========================= >


Je pencherais pour un comportement non défini, et non pour un bug.
La norme defini-t-elle les valeurs signées binaires ? la rotation de ces
memes valeurs ?

Ne pouvant le tester par moi-même ( VC6, seulement), une solution serait de
caster en unsigned :
int nResult = (( ((unsigned int) nValue) >>31)^nValue)>>4;

Pierre Maurette
Le #722067
"K. Ahausse"

Testez par vous même:

#include #include void main()
{
int nValue;
printf("entrey la valeur -1024 et comparez le resultat entre debug et
releasen");
scanf("%d",&nValue);
int nResult = ((nValue>>31)^nValue)>>4;
printf("Resultat %dn",nResult);
}
========================= >>


Je pencherais pour un comportement non défini, et non pour un bug.
La norme defini-t-elle les valeurs signées binaires ? la rotation de ces
memes valeurs ?

Ne pouvant le tester par moi-même ( VC6, seulement), une solution serait de
caster en unsigned :
int nResult = (( ((unsigned int) nValue) >>31)^nValue)>>4;
Extrait du post de Loïc Joly (et de C++98 5.8.3):

"...If E1 has a signed type and a negative value, the resulting value
is implementation defined."

Implementation defined behavior != undefined behavior (C++98 1.3.5 et
1.3.12)

Extrait de mon post précédent (et de la documentation de
l'implémentation, où le "behavior" est "defined") :

The right shift operator causes the bit pattern in the first operand
to be shifted right the number of bits specified by the second
operand. Bits vacated by the shift operation are zero-filled for
unsigned quantities. For signed quantities, the sign bit is propagated
into the vacated bit positions. The shift is a logical shift if the
left operand is an unsigned quantity; otherwise, it is an arithmetic
shift.

Microsoft Specific

The result of a right shift of a signed negative quantity is
implementation dependent. Although Microsoft C++ propagates the
most-significant bit to fill vacated bit positions, there is no
guarantee that other implementations will do likewise.

END Microsoft Specific

Extrait d'un post précédent (test sur VC++7.1) :
<code>
#include
int main(void)
{
int nResult;
int nValue1;
const int nValue2 = -1024;
static const int nValue3 = -1024;
nValue1 = -1024;
nResult = ((nValue1>>31)^nValue1)>>4;
printf("Resultat %dn",nResult);
nResult = ((nValue2>>31)^nValue2)>>4;
printf("Resultat %dn",nResult);
nResult = ((nValue3>>31)^nValue3)>>4;
printf("Resultat %dn",nResult);
return 0;
}
</code>

(J'ai regardé le code machine pour voir ce qui est évalué au
compil-time et ce qui calculé au run-time)

Le fichier est nommé test.cpp :
Le résultat en release est :
Resultat -64
Resultat 63
Resultat 63
nValue1 est évaluée incorrectement (par l'"optimiseur" ?).
nValue2 est évaluée correctement par le compilateur.
nValue3 est évaluée correctement par le compilateur.
Le résultat en debug est logiquement :
Resultat 63
Resultat 63
Resultat 63
nValue1 est calculée correctement au run-time par le code généré.
nValue2 est évaluée correctement par le compilateur.
nValue3 est évaluée correctement par le compilateur.

Le fichier est nommé test.c :
Le résultat en release est :
Resultat -64
Resultat -64
Resultat 63
nValue1 est évaluée incorrectement (par l'"optimiseur" ?).
nValue2 est évaluée incorrectement (par l'"optimiseur" ?).
nValue3 est évaluée correctement par le compilateur.
Le résultat en debug est logiquement :
Resultat 63
Resultat 63
Resultat 63
nValue1 est calculée correctement au run-time par le code généré.
nValue2 est calculée correctement au run-time par le code généré.
nValue3 est calculée correctement au run-time par le code généré.

(seul compte pour la question "bug ou pas bug" le comportement, on se
fiche du code généré)

Alors? Ça sent le bug ?
--
Pierre


Mickael Pointier
Le #722066
K. Ahausse wrote:
Testez par vous même:

#include #include void main()
{
int nValue;
printf("entrey la valeur -1024 et comparez le resultat entre
debug et releasen");
scanf("%d",&nValue);
int nResult = ((nValue>>31)^nValue)>>4;
printf("Resultat %dn",nResult);
}
========================= >>


Je pencherais pour un comportement non défini, et non pour un bug.
La norme defini-t-elle les valeurs signées binaires ? la rotation de
ces memes valeurs ?

Ne pouvant le tester par moi-même ( VC6, seulement), une solution
serait de caster en unsigned :
int nResult = (( ((unsigned int) nValue) >>31)^nValue)>>4;


Le problème c'est qu'en faisant ca tu changes la sémantique du décallage.
Là tu vas te retrouver avec 31 bits à 0 (et un valant la valeur du 31ème bit
avant le décallage), alors qu'avec le décallage en signé tu te retrouves
avec 32 bits valant la valeur du 31ème bit avant le décallage.

Ca ne donne pas du tout la même chose :)

Mike


kanze
Le #722065
Pierre Maurette news:
Loïc Joly
Mickael Pointier wrote:



[...]
The value of E1 >> E2 is E1 rightshifted E2 bit positions. If E1 has
an unsigned type or if E1 has a signed type and a nonnegative value,
the value of the result is the integral part of the quotient of E1
divided by the quantity 2 raised to the power E2. If E1 has a signed
type and a negative value, the resulting value is
implementationdefined.


Ne pas confondre bug et non-conformité à la norme. Le bug est
pratiquement un "délit commercial", à juger par rapport à un contrat.


Un contrat, ou simplement les prétensions du compilateur. (Selon le
contrat, on ne garantit rien.) Mais le point est bien pris :
non-conformité n'est qu'une erreur que si le compilateur se dit conforme
(c-à-d pour Comeau seulement -- mais la plupart prétend à une conformité
partielle). En revanche, si le compilateur dit fait X, et ne le fait
pas, c'est une erreur, que X fasse partie de la norme ou non.

La norme dit "implementation defined", l'implémentation documente :


En effet, la norme exige que les choix des « implementation defined »
soient documentés ; un compilateur qui ne les documente pas n'est pas
conforme, et un compilateur dont le code généré n'est pas conforme à son
documentation contient une erreur.


The right shift operator causes the bit pattern in the first operand
to be shifted right the number of bits specified by the second
operand. Bits vacated by the shift operation are zero-filled for
unsigned quantities. For signed quantities, the sign bit is propagated
into the vacated bit positions. The shift is a logical shift if the
left operand is an unsigned quantity; otherwise, it is an arithmetic
shift.

Le "contrat" est clair. J'intuite qu'il est le même pour toutes les
implémentations "complément à 2".


Je ne vois pas pourquoi. La motivation de l'« implementation defined »,
c'est qu'il existe des machines où il n'y a pas d'instruction de
décalage « arithmétique » ; l'implémenter en software coûtera donc en
temps d'exécution. Y compris dans le cas où je sais que la valeur serait
toujours positive. Alors, dans la vieille tradition de C, simple et vite
sont plus important que correct. Ici, la norme C++ reprend integralement
ce que dit la norme C. Qui a son tour reprend integralement le K&R I.

--
James Kanze GABI Software mailto:
Conseils en informatique orientée objet/ http://www.gabi-soft.fr
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


K. Ahausse
Le #722064
"Pierre Maurette" news:
"K. Ahausse"

Testez par vous même:

#include #include void main()
{
int nValue;
printf("entrey la valeur -1024 et comparez le resultat entre debug
et



releasen");
scanf("%d",&nValue);
int nResult = ((nValue>>31)^nValue)>>4;
printf("Resultat %dn",nResult);
}
========================= > >>


Je pencherais pour un comportement non défini, et non pour un bug.
La norme defini-t-elle les valeurs signées binaires ? la rotation de ces
memes valeurs ?

Ne pouvant le tester par moi-même ( VC6, seulement), une solution serait
de


caster en unsigned :
int nResult = (( ((unsigned int) nValue) >>31)^nValue)>>4;
Extrait du post de Loïc Joly (et de C++98 5.8.3):

"...If E1 has a signed type and a negative value, the resulting value
is implementation defined."

Implementation defined behavior != undefined behavior (C++98 1.3.5 et
1.3.12)

Extrait de mon post précédent (et de la documentation de
l'implémentation, où le "behavior" est "defined") :

The right shift operator causes the bit pattern in the first operand
to be shifted right the number of bits specified by the second
operand. Bits vacated by the shift operation are zero-filled for
unsigned quantities. For signed quantities, the sign bit is propagated
into the vacated bit positions. The shift is a logical shift if the
left operand is an unsigned quantity; otherwise, it is an arithmetic
shift.

Microsoft Specific

The result of a right shift of a signed negative quantity is
implementation dependent. Although Microsoft C++ propagates the
most-significant bit to fill vacated bit positions, there is no
guarantee that other implementations will do likewise.

END Microsoft Specific


Comme tout cela manque un peu de clarté, et en contradiction avec les faits
exposés, je me permettais de suggérer l'utilisation de valeur non signées.
Peut-etre cette solution "simpliste" eut-elle convenue à l'OP.

Quant à l'interprétation de la norme, c'est un sujet qui me dépasse, et je
regrette d'en avoir fait mention dans ma réponse.
Je suis programmeur pas juriste :-)



adebaene
Le #722063
"Mickael Pointier"
Salut.

J'ai un de mes collègues qui est tombé sur ce qui ressemble à un bug de
Visual.net (non présent dans Visual 6), mais bon, c'est peut-être bettement
que ce qu'il a fait à un comportement indéfini, donc je préfère demander ici
avant éventuellement de reporter le bug à qui de droit.


Ca vaudrait le coup de poster sur microsoft.public.vc.language pour
vérification AMHA.

Arnaud

Publicité
Poster une réponse
Anonyme