web-dev-qa-db-fra.com

Pourquoi le décalage à gauche, "<<", pour les entiers 32 bits ne fonctionne-t-il pas comme prévu lorsqu'il est utilisé plus de 32 fois?

Lorsque j'écris le programme suivant et que j'utilise le compilateur GNU C++, la sortie est 1 qui je pense est dû à l'opération de rotation effectuée par le compilateur.

#include <iostream>

int main()
{
    int a = 1;
    std::cout << (a << 32) << std::endl;

    return 0;
}

Mais logiquement, comme on dit que les bits sont perdus s'ils dépassent la largeur des bits, la sortie doit être 0. Que se passe-t-il?

Le code est sur ideone, http://ideone.com/VPTwj .

58
AnkitSablok

Cela est dû à une combinaison d'un comportement indéfini en C et au fait que le code généré pour les processeurs IA-32 a un masque de 5 bits appliqué sur le nombre de décalage. Cela signifie que sur les processeurs IA-32, la plage d'un compte de décalage est 0-31 uniquement. 1

De Le langage de programmation C 2

Le résultat n'est pas défini si l'opérande de droite est négatif, ou supérieur ou égal au nombre de bits du type de l'expression de gauche.

De Manuel du développeur du logiciel d'architecture Intel IA-32 3

Le 8086 ne masque pas le nombre de décalages. Cependant, tous les autres processeurs IA-32 (à commencer par le processeur Intel 286) masquent le nombre de décalage à 5 bits, ce qui donne un nombre maximal de 31. Ce masquage est effectué dans tous les modes de fonctionnement (y compris le mode virtuel-8086) pour réduire le temps d'exécution maximal des instructions.



1 http://codeyarns.com/2004/12/20/c-shift-operator-mayhem/

2 A7.8 Opérateurs de quart, Annexe A. Manuel de référence, langage de programmation C

3 SAL/SAR/SHL/SHR - Shift, Chapitre 4. Référence du jeu d'instructions, IA-32 Intel Architecture Software Developer’s Manual

41
Ashwin Nanjappa

En C++, shift n'est bien défini que si vous décalez une valeur de moins d'étapes que la taille du type. Si int est 32 bits, alors seulement 0 à, et y compris, 31 étapes est bien défini.

Alors pourquoi ça?

Si vous jetez un œil au matériel sous-jacent qui effectue le décalage, s'il n'a qu'à regarder les cinq bits inférieurs d'une valeur (dans le cas de 32 bits), il peut être implémenté en utilisant moins de portes logiques que s'il doit inspecter chaque bit de la valeur.

Réponse à la question en commentaire

C et C++ sont conçus pour fonctionner aussi rapidement que possible, sur n'importe quel matériel disponible. Aujourd'hui, le code généré est simplement une instruction `` shift '', quelle que soit la façon dont le matériel sous-jacent gère les valeurs en dehors de la plage spécifiée. Si les langues avaient spécifié le comportement du décalage, le générateur pourrait devoir vérifier que le nombre de décalages est dans la plage avant d'effectuer le décalage. En règle générale, cela donnerait trois instructions (comparer, dériver, déplacer). (Certes, dans ce cas, cela ne serait pas nécessaire car le nombre de postes est connu.)

43
Lindydancer

C'est un comportement indéfini selon la norme C++:

La valeur de E1 << E2 est E1 positions de bits E2 décalées vers la gauche; les bits libérés sont remplis de zéro. Si E1 a un type non signé, la valeur du résultat est E1 × 2 ^ E2, modulo réduit un de plus que la valeur maximale représentable dans le type de résultat. Sinon, si E1 a un type signé et une valeur non négative, et E1 × 2 ^ E2 est représentable dans le type de résultat, alors c'est la valeur résultante; sinon, le comportement n'est pas défini.

21
David Heffernan

Les réponses de Lindydancer et 6502 expliquent pourquoi (sur certaines machines) il s'agit d'un 1 en cours d'impression (bien que le comportement de l'opération ne soit pas défini). J'ajoute les détails au cas où ils ne seraient pas évidents.

Je suppose que (comme moi) vous exécutez le programme sur un processeur Intel. GCC génère ces instructions d'assemblage pour l'opération de décalage:

movl $32, %ecx
sall %cl, %eax

Sur le sujet de sall et d'autres opérations de décalage, la page 624 du Manuel de référence du jeu d'instructions dit:

Le 8086 ne masque pas le nombre de décalages. Cependant, tous les autres processeurs Intel Architecture (à commencer par le processeur Intel 286) masquent le nombre de décalage à cinq bits, ce qui donne un nombre maximal de 31. Ce masquage est effectué dans tous les modes de fonctionnement (y compris le mode virtuel-8086) pour réduire le temps d'exécution maximal des instructions.

Puisque les 5 bits inférieurs de 32 sont nuls, alors 1 << 32 est équivalent à 1 << 0, lequel est 1.

En expérimentant avec un plus grand nombre, nous prédirions que

cout << (a << 32) << " " << (a << 33) << " " << (a << 34) << "\n";

imprimerait 1 2 4, et c'est bien ce qui se passe sur ma machine.

13
antonakos

Cela ne fonctionne pas comme prévu car vous en attendez trop.

Dans le cas de x86, le matériel ne se soucie pas des opérations de décalage lorsque le compteur est plus grand que la taille du registre (voir par exemple la description de l'instruction SHL sur documentation de référence x86 pour une explication).

La norme C++ ne voulait pas imposer de coût supplémentaire en disant quoi faire dans ces cas car le code généré aurait été obligé d'ajouter des vérifications et une logique supplémentaires pour chaque décalage paramétrique.

Avec cette liberté, les implémenteurs de compilateurs peuvent générer une seule instruction d'assemblage sans test ni branche.

Une approche plus "utile" et "logique" aurait été par exemple d'avoir (x << y) équivalent à (x >> -y) et aussi la gestion des compteurs hauts avec un comportement logique et cohérent.

Cependant, cela aurait nécessité une manipulation beaucoup plus lente pour le décalage de bits, le choix était donc de faire ce que le matériel fait, laissant aux programmeurs le besoin d'écrire leurs propres fonctions pour les cas secondaires.

Étant donné que le matériel différent fait des choses différentes dans ces cas, ce que la norme dit est fondamentalement "Quoi qu'il arrive quand vous faites des choses étranges, ne blâmez pas C++, c'est votre faute" traduit en juridique.

9
6502

Décaler une variable de 32 bits de 32 bits ou plus est un comportement non défini et peut amener le compilateur à faire sortir les démons de votre nez.

Sérieusement, la plupart du temps, la sortie sera 0 (si int est de 32 bits ou moins) car vous déplacez le 1 jusqu'à ce qu'il retombe et qu'il ne reste plus que 0. Mais le compilateur peut l'optimiser pour faire ce qu'il veut.

Voir l'excellente entrée du blog LLVM Ce que tout programmeur C devrait savoir sur le comportement indéfini , une lecture incontournable pour chaque développeur C.

8
DarkDust

Puisque vous décalez un bit d'un entier de 32 bits; tu auras: warning C4293: '<<' : shift count negative or too big, undefined behavior en VS. Cela signifie que vous passez au-delà de l'entier et que la réponse pourrait être TOUT, car il s'agit d'un comportement indéfini.

5
Bob2Chiv

Vous pouvez essayer ce qui suit. Cela donne en fait la sortie comme 0 après 32 quarts de gauche.

#include<iostream>
#include<cstdio>

using namespace std;

int main()
{
  int a = 1;
  a <<= 31;
  cout << (a <<= 1);
  return 0;
}
0
Sandip Agarwal

J'ai eu le même problème et cela a fonctionné pour moi:

f = ((long long) 1 << (i-1));

Où i peut être n'importe quel entier supérieur à 32 bits. Le 1 doit être un entier de 64 bits pour le passage au travail.

0
Pieter888