2017-10-06 4 views
-2

J'essaie de travailler avec structure de données de tableau de bits en C++. Il est simple curiosité, mais comment dois-je expliquer:Pourquoi l'opérateur de décalage semble-t-il faire un cycle de 64 bits entier?

uint64_t a = 1; 
uint64_t b = a << 1; 

cout << (a == (a << 64)) << endl; // get 1 
cout << (a == (b << 63)) << endl; // get 0 

On dirait un < < x est cyclique lorsque x >= 64, mais pads avec des zéros lorsque x < 64. Ai-je tort ?

Sinon, quelle est l'explication? Je pensais que les entiers 64bits n'étaient pas naturellement cycliques.

Répondre

5

Selon [expr.shift]:

le comportement est indéfini si l'opérande droit est négatif, ou supérieure ou égale à la longueur en bits de l'opérande gauche promu.

Par conséquent, ce comportement est indéfini:

uint64_t a = 1; 
a << 64; 
+0

Pouvez-vous répondre à l'autre moitié de cette question, le comportement de '(a << 1) << 63'? –

+0

@ Robᵩ Il n'y a rien à expliquer: c'est parfaitement prévu. L'esprit de la question telle que je l'ai comprise est: _Pourquoi est-ce que le fait de déplacer 1 par 64 bits en une opération est différent de le faire en deux? _ – YSC

1

Comme expliqué CSY, le déplacement en une seule opération de plus de la taille des caractères est un comportement non défini; cette règle vient du désir de mapper des opérateurs de décalage de bits directement aux instructions de code machine, qui ont un comportement différent dans de tels cas dépendant du processeur.

Par exemple, sur x86 les masques d'instruction SHL la quantité de décalage avec 63 (lors du fonctionnement sur un registre 64 bits), ce qui est probablement la raison pour laquelle vous voyez que a<<64 reste 1 (64 & 63 == 0, par conséquent, il est effectivement un no- op). Notez qu'il ne s'agit que d'une explication pédagogique généralement valable pour les cas simples (généralement avec des optimisations désactivées ou avec des optimisations activées mais décalage inconnu, donc lorsque le décalage correspond à l'opcode de décalage de plate-forme sous-jacent). Lorsque vous déplacez des constantes avec des constantes, le compilateur peut propager les valeurs et effectuer une arithmétique interne avec une plus grande précision ou, même dans le cas général, émettre du code qui fonctionne dans des registres supérieurs à votre type de données et tronquer à la fin. mappez un décalage d'un uint32_t à un décalage de registre complet de 64 bits, bien que pas particulièrement intelligent), donnant ainsi des résultats différents dans ces cas hors spécifications. Rappelez-vous: le comportement indéfini n'est pas défini, vous ne pouvez pas vraiment vous attendre à ce que quelque chose de spécifique se produise. D'autre part, faire l'opération en deux étapes fonctionne comme prévu, car les deux opérations sont bien définies (elles se remplissent de zéros sur la droite, en jetant les bits sur la gauche).

+1

Ceci est une bonne explication de _why_ cela a été choisi pour être UB. +1 – YSC