OVH Cloud OVH Cloud

Decalages d'un nombre de bits pouvant etre >= a la taille du type

134 réponses
Avatar
Vincent Lefevre
J'ai besoin de faire un décalage vers la droite sur un type entier
(disons non signé) générique (i.e. défini par typedef) d'un certain
nombre constant de bits. Le problème est que ce nombre de bits peut
être supérieur ou égal à la taille du type en question. Une idée
sur la façon de faire ça efficacement sans obtenir de comportement
indéfini et sans passer par un outil du style "configure"?

--
Vincent Lefèvre <vincent@vinc17.org> - Web: <http://www.vinc17.org/>
100% accessible validated (X)HTML - Blog: <http://www.vinc17.org/blog/>
Work: CR INRIA - computer arithmetic / SPACES project at LORIA

10 réponses

1 2 3 4 5
Avatar
Vincent Lefevre
Dans l'article <cjusr0$k5$,
Charlie Gordon écrit:

La macro évidente :

#define SAFE_RSHIFT(v, c) ((sizeof(v)*CHAR_BIT >= (c)) ? 0 : (v) >> (c))

a plusieurs inconvénients :
- la comparaison est faite à l'envers, ce qui est facile à corriger ;-)
- c est instancié deux fois.
- on a potentiellement un comportement indéfini sur une partie de
l'expression.
- on a un warning stupide dans ce cas si c est constant et que le
compilateur se croit malin.


Ce n'est pas un warning stupide puisqu'il est justifié: comportement
indéfini (même si en pratique, il y a peu de chance que cela ait un
effet non voulu).

- on peut aussi avoir un warning si c est une expression d'un type signé à
cause de la comparaison signé/non-signé
- le code généré est inefficace si c est une expression non constante (2
jump)


Le compilo pourrait toujours optimiser.

On peut remédier à ces inconvénients comme ceci:
- on utilise une fonction inline au lieu d'une macro, mais cela force le
type de v, ce qui n'est pas forcement loisible
- on peut masquer c dans le shift, ce qui supprime le warning sans
changer le code pour les valeurs constantes de c, mais ralentit un
peu les instances ou c est non constante, à moins que le compilo
soit très fort et que l'architecture s'y prete. On peut aussi
utiliser 2 macros differentes pour les shifts constants et calculés.


Le masquage est une bonne idée (on doit supposer qu'il n'y a pas de bits
de padding, ce n'est donc pas idéal, mais c'est mieux que rien). Mieux,
faire un modulo, que le compilo sera capable d'optimiser si possible et
qui fonctionnera aussi si la taille n'est pas une puissance de 2.

- on caste (c) en (unsigned)


En quoi cela change quelque chose? (On suppose que c est positif, sinon
le décalage n'a pas de sens implicite.)

- on peut faire une expression sans test, ce qui generera un
meilleur code si le compilo est subtil.


Non, comportement indéfini (pire qu'avec le test).

[...]
Ceci suppose que le shift signé duplique le bit de signe. Donc à
n'utiliser que sur les architectures où cette supposition est valide


Je déconseille fortement de se baser là-dessus. Noter que ça peut aussi
dépendre du compilo.

PS: je trouve regrettable que le shift d'une quantité *constante*
supérieure à la taille du type non soit pas défini avec la
sémantique évidente, mais je suppose que c'est pour préserver la
cohérence et l'efficacité de l'implémentation.


Oui, probablement. D'un autre côté, on peut aussi perdre en efficacité
sur certaines archi à cause de ce choix. Le problème est que le C
n'est pas un langage suffisamment riche pour permettre à l'utilisateur
d'exprimer tout ce qu'il sait et que le compilo ne peut pas deviner.

--
Vincent Lefèvre - Web: <http://www.vinc17.org/>
100% accessible validated (X)HTML - Blog: <http://www.vinc17.org/blog/>
Work: CR INRIA - computer arithmetic / SPACES project at LORIA

Avatar
Richard Delorme
Dans l'article <cjucbf$lub$,
Laurent Deniau écrit:


Defini dans tous les cas mais ne fait pas necessairement ce que tu veux
en >32 bits d'ou ma suggestion initiale de passer par le preprocesseur
pour selectionner le code correct. Idem pour le warning du compilo.



Non, la norme dit bien que c'est du comportement indéfini.

6.5.7 Bitwise shift operators
[...]
[#3] The integer promotions are performed on each of the
operands. The type of the result is that of the promoted
left operand. If the value of the right operand is negative
or is greater than or equal to the width of the promoted
left operand, the behavior is undefined.



Si, comme dans l'exemple de Laurent Deniau, on teste la valeur pour
n'effectuer le décalage que si elle convient, il n'y a plus de
comportement indéfini. Ou alors j'ai manqué un épisode ?

--
Richard


Avatar
Charlie Gordon
"Vincent Lefevre" <vincent+ wrote in message
news:20041005202822$
Dans l'article <cjusr0$k5$,
Charlie Gordon écrit:

La macro évidente :

#define SAFE_RSHIFT(v, c) ((sizeof(v)*CHAR_BIT >= (c)) ? 0 : (v) >>
(c))



- la comparaison est faite à l'envers, ce qui est facile à corriger ;-)
- on a potentiellement un comportement indéfini sur une partie de
l'expression.
- on a un warning stupide dans ce cas si c est constant et que le
compilateur se croit malin.


Ce n'est pas un warning stupide puisqu'il est justifié: comportement
indéfini (même si en pratique, il y a peu de chance que cela ait un
effet non voulu).


Merci d'avoir attrappé la perche tendue !
Ce warning est stupide parce qu'il n'est produit que pour des valeurs
constantes de c qui sont plus grandes que la taille du type. Or le test
fait que le comportement indéfini n'est pas invoqué dans ce cas, et le
compilateur ne devrait même pas en générer le code puisque la comparaison a
un résultat connu à compile time. Tu dis avoir eu ce warning, mais
peut-être disparait-il quand tu corriges le test inversé.

Le masquage est une bonne idée (on doit supposer qu'il n'y a pas de bits
de padding, ce n'est donc pas idéal, mais c'est mieux que rien). Mieux,
faire un modulo, que le compilo sera capable d'optimiser si possible et
qui fonctionnera aussi si la taille n'est pas une puissance de 2.


C'est de l'intégrisme !
De quels bits de padding parles-tu ?
Et il faut impérativement caster (c) en (unsigned) pour que le modulo soit
toujours optimisé en un masque sur les architectures où la taille est une
puissance de 2 (connais-tu des contre-exemples ?), sinon le code généré pour
(c) expression signée non constante sera inefficace.

- on caste (c) en (unsigned)


En quoi cela change quelque chose? (On suppose que c est positif, sinon
le décalage n'a pas de sens implicite.)


si c est une expression de type signé, par exemple une variable int, le
compilateur peut protester que la comparaison d'un int et d'un size_t est
suspecte, indépendemment du signe de la valeur qui doit bien entendu être
positive.
D'ailleurs une valeur négative de c serait vue dans cette comparaison avec
ou sans cast comme une très grande valeur positive, ce qui n'est pas
intuitif n'est-ce pas. Le cast ne change pas la sémantique, il sert
simplement à supprimer le warning.

- on peut faire une expression sans test, ce qui generera un
meilleur code si le compilo est subtil.


Non, comportement indéfini (pire qu'avec le test).


#define SAFE_SHR(v, c) (-(sizeof(v) * CHAR_BIT < (unsigned)(c)) & ((v) >>
((c) & (sizeof(v) * CHAR_BIT - 1)))

Avec cette version, le comportement n'est pas indéfini.

Chqrlie.


Avatar
Vincent Lefevre
Dans l'article <cjv6en$20n$,
Charlie Gordon écrit:

Ce warning est stupide parce qu'il n'est produit que pour des valeurs
constantes de c qui sont plus grandes que la taille du type. Or le test
fait que le comportement indéfini n'est pas invoqué dans ce cas,


Il l'est. Le test n'a aucun influence ici. Le compilo peut très
bien évaluer des expressions au moment de la traduction (cas des
expressions constantes). Comme pour chaque valeur du terme de
gauche il y a comportement indéfini, l'implémentation peut faire
ce qu'elle veut, à savoir, considérer que la valeur est constante.
Dans ce cas, toutes les contraintes du 6.6 sont satisfaites.

et le compilateur ne devrait même pas en générer le code puisque la
comparaison a un résultat connu à compile time.


Il y a ce que fait le compilateur et ce que dit la norme.

Tu dis avoir eu ce warning, mais peut-être disparait-il quand tu
corriges le test inversé.


Je n'ai pas utilisé cette macro, mais du code où je faisais déjà
un test équivalent (avant d'initier cette enfilade). Le warning
est bien là.

De quels bits de padding parles-tu ?


C'est autorisé, mais bon...

Et il faut impérativement caster (c) en (unsigned) pour que le
modulo soit toujours optimisé en un masque sur les architectures où
la taille est une puissance de 2 (connais-tu des contre-exemples ?),
sinon le code généré pour (c) expression signée non constante sera
inefficace.


Faux. Le compilo peut très bien supposer que c est positif et donc
faire comme si c était non signé. Si c est strictement négatif, c'est
du comportement indéfini et ce n'était donc pas une erreur d'avoir
fait la supposition c >= 0.

- on caste (c) en (unsigned)


En quoi cela change quelque chose? (On suppose que c est positif, sinon
le décalage n'a pas de sens implicite.)


si c est une expression de type signé, par exemple une variable int,
le compilateur peut protester que la comparaison d'un int et d'un
size_t est suspecte, indépendemment du signe de la valeur qui doit
bien entendu être positive.


Non, il n'a pas le droit de protester.

Le cast ne change pas la sémantique, il sert simplement à supprimer
le warning.


Mieux vaut changer de compilo.

#define SAFE_SHR(v, c) (-(sizeof(v) * CHAR_BIT < (unsigned)(c)) & ((v) >>
((c) & (sizeof(v) * CHAR_BIT - 1)))

Avec cette version, le comportement n'est pas indéfini.


Il est au moins dépendant de l'implémentation.

... (c) % (sizeof(v) * CHAR_BIT) est plus lisible et plus portable.

L'astuce du - est jolie, mais un peu obfuscated, et ne s'optimise
peut-être pas aussi bien qu'un ? :, mais je n'ai pas fait de test.

--
Vincent Lefèvre - Web: <http://www.vinc17.org/>
100% accessible validated (X)HTML - Blog: <http://www.vinc17.org/blog/>
Work: CR INRIA - computer arithmetic / SPACES project at LORIA



Avatar
Vincent Lefevre
Dans l'article <41630d88$0$15749$,
Richard Delorme écrit:

Si, comme dans l'exemple de Laurent Deniau, on teste la valeur pour
n'effectuer le décalage que si elle convient, il n'y a plus de
comportement indéfini. Ou alors j'ai manqué un épisode ?


Oui, la section 6.6 de la norme. Cf ma réponse à Charlie sur ce point.

--
Vincent Lefèvre - Web: <http://www.vinc17.org/>
100% accessible validated (X)HTML - Blog: <http://www.vinc17.org/blog/>
Work: CR INRIA - computer arithmetic / SPACES project at LORIA

Avatar
Charlie Gordon
"Vincent Lefevre" <vincent+ wrote in message
news:20041005221856$
Dans l'article <cjv6en$20n$,
Charlie Gordon écrit:

Ce warning est stupide parce qu'il n'est produit que pour des valeurs
constantes de c qui sont plus grandes que la taille du type. Or le test
fait que le comportement indéfini n'est pas invoqué dans ce cas,


Il l'est. Le test n'a aucun influence ici. Le compilo peut très
bien évaluer des expressions au moment de la traduction (cas des
expressions constantes). Comme pour chaque valeur du terme de
gauche il y a comportement indéfini, l'implémentation peut faire
ce qu'elle veut, à savoir, considérer que la valeur est constante.
Dans ce cas, toutes les contraintes du 6.6 sont satisfaites.


Pas du tout ! C'est le code produit qui a un comportement indéfini, pas le
compilateur.
Il peut considérer ce qu'il veut du terme indéfini, le test fait que
celui-ci n'est pas exécuté.
L'opérateur ?: n'évalue que 2 des 3 termes, et dans le cas présent il
n'évalue jamais le terme de droite dont le comportement est indéfini.

Et il faut impérativement caster (c) en (unsigned) pour que le
modulo soit toujours optimisé en un masque sur les architectures où
la taille est une puissance de 2 (connais-tu des contre-exemples ?),
sinon le code généré pour (c) expression signée non constante sera
inefficace.


Faux. Le compilo peut très bien supposer que c est positif et donc
faire comme si c était non signé. Si c est strictement négatif, c'est
du comportement indéfini et ce n'était donc pas une erreur d'avoir
fait la supposition c >= 0.


C'est inexact ! c'est du résultat du modulo que le compilateur pourrait
supposer qu'il fût nécessairement positif. Cela n'implique pas forcément
que la division soit non-signée. Mettons que cet argument n'est pas moins
tiré par les cheveux que le tien.

- on caste (c) en (unsigned)


En quoi cela change quelque chose? (On suppose que c est positif,
sinon



le décalage n'a pas de sens implicite.)


si c est une expression de type signé, par exemple une variable int,
le compilateur peut protester que la comparaison d'un int et d'un
size_t est suspecte, indépendemment du signe de la valeur qui doit
bien entendu être positive.


Non, il n'a pas le droit de protester.


Tu n'est pas obligé de le lui demander, gcc a un flag spécifique pour ce
warning assez contraignant, mais il dénonce parfois fort justement du code
subtilement erroné !

#define SAFE_SHR(v, c) (-(sizeof(v) * CHAR_BIT < (unsigned)(c)) & ((v)

((c) & (sizeof(v) * CHAR_BIT - 1)))


L'astuce du - est jolie, mais un peu obfuscated, et ne s'optimise
peut-être pas aussi bien qu'un ? :, mais je n'ai pas fait de test.

cmp eax,edx

sbb eax,eax


Chqrlie.




Avatar
Charlie Gordon
"Charlie Gordon" wrote in message
news:cjvb19$d09$
"Vincent Lefevre" <vincent+ wrote in message
news:20041005221856$
Dans l'article <cjv6en$20n$,
Charlie Gordon écrit:

Ce warning est stupide parce qu'il n'est produit que pour des valeurs
constantes de c qui sont plus grandes que la taille du type. Or le
test



fait que le comportement indéfini n'est pas invoqué dans ce cas,


Il l'est. Le test n'a aucun influence ici. Le compilo peut très
bien évaluer des expressions au moment de la traduction (cas des
expressions constantes). Comme pour chaque valeur du terme de
gauche il y a comportement indéfini, l'implémentation peut faire
ce qu'elle veut, à savoir, considérer que la valeur est constante.
Dans ce cas, toutes les contraintes du 6.6 sont satisfaites.


Pas du tout ! C'est le code produit qui a un comportement indéfini, pas
le

compilateur.
Il peut considérer ce qu'il veut du terme indéfini, le test fait que
celui-ci n'est pas exécuté.
L'opérateur ?: n'évalue que 2 des 3 termes, et dans le cas présent il
n'évalue jamais le terme de droite dont le comportement est indéfini.


Après relecture du standard, il apparait que 1>>32 c'est undefined behavior,
y compris pendant la translation.
Je pense quand même que 1 ? 0 : 1>>32 est sémantiquement correct,
d'ailleurs 6.6 paragraphe 3 prévoit le cas d'expressions constantes dont des
sous-expressions non constantes ne sont jamais évaluées. Par exemple :

3 * sizeof(main(argc,argv))
1 ? 0 : main(argc,argv)

alors pourquoi pas

1 ? 0 : 1>>32

Chqrlie.



Avatar
Gabriel Dos Reis
Vincent Lefevre <vincent+ writes:

| Dans l'article <cju88c$d81$,
| Laurent Deniau écrit:
|
| > As-tu lu mon post jusqu'au bout?
|
| J'ai dû mal comprendre ton post alors. Donc si tu sous-entends
| qu'il faut rajouter du code à chaque fois qu'on a une taille
| différente (et encore, je ne vois pas en quoi cela résout le
| problème), ce n'est pas une bonne solution. Je veux quelque
| chose de 100% portable.

Alors va faire du Java <g>

-- Gaby
Avatar
Gabriel Dos Reis
Vincent Lefevre <vincent+ writes:

| Dans l'article <cjusr0$k5$,
| Charlie Gordon écrit:
|
| > La macro évidente :
|
| > #define SAFE_RSHIFT(v, c) ((sizeof(v)*CHAR_BIT >= (c)) ? 0 : (v) >> (c))
|
| > a plusieurs inconvénients :
| > - la comparaison est faite à l'envers, ce qui est facile à corriger ;-)
| > - c est instancié deux fois.
| > - on a potentiellement un comportement indéfini sur une partie de
| > l'expression.
| > - on a un warning stupide dans ce cas si c est constant et que le
| > compilateur se croit malin.
|
| Ce n'est pas un warning stupide puisqu'il est justifié: comportement
| indéfini (même si en pratique, il y a peu de chance que cela ait un
| effet non voulu).

Donc, c'est un warning stupide.

-- Gaby
Avatar
Gabriel Dos Reis
Vincent Lefevre <vincent+ writes:

| Dans l'article <cjv6en$20n$,
| Charlie Gordon écrit:
|
| > Ce warning est stupide parce qu'il n'est produit que pour des valeurs
| > constantes de c qui sont plus grandes que la taille du type. Or le test
| > fait que le comportement indéfini n'est pas invoqué dans ce cas,
|
| Il l'est. Le test n'a aucun influence ici. Le compilo peut très
| bien évaluer des expressions au moment de la traduction (cas des
| expressions constantes). Comme pour chaque valeur du terme de
| gauche il y a comportement indéfini, l'implémentation peut faire
| ce qu'elle veut, à savoir, considérer que la valeur est constante.
| Dans ce cas, toutes les contraintes du 6.6 sont satisfaites.

Et tu crois que quand les conditions 6.6 et machin.chose sont réunies,
le compilateur va produire un exécutable ?

[...]

| > De quels bits de padding parles-tu ?
|
| C'est autorisé, mais bon...

yep, mais bon.

[...]

| > Avec cette version, le comportement n'est pas indéfini.
|
| Il est au moins dépendant de l'implémentation.

Huh.

-- Gaby
1 2 3 4 5