Le bit-shift dépend-il de l'endianness?

155

Supposons que j'ai le nombre 'numb'=1025 [00000000 00000000 00000100 00000001]représenté:

Sur la machine Little-Endian:

00000001 00000100 00000000 00000000

Sur une machine Big-Endian:

00000000 00000000 00000100 00000001

Maintenant, si j'applique Left Shift sur 10 bits (ie: numb << = 10), je devrais avoir:

[A] Sur la machine Little-Endian:

Comme je l'ai remarqué dans GDB, Little Endian effectue le décalage vers la gauche en 3 étapes: [J'ai montré les étapes «3» pour mieux comprendre le traitement uniquement]

  1. Traitez le non. dans la Convention Big-Endian:

    00000000        00000000        00000100    00000001
  2. Appliquer le décalage vers la gauche:

    00000000        00010000        00000100        00000000
  3. Représentez à nouveau le résultat en Little-Endian:

    00000000        00000100        00010000        00000000 

[B]. Sur une machine Big-Endian:

00000000        00010000        00000100        00000000

Ma question est:

Si j'applique directement un décalage à gauche sur la convention Little Endian, cela devrait donner:

numb:

00000001 00000100 00000000 00000000

numb << 10:

00010000 00000000 00000000 00000000

Mais en fait, cela donne:

00000000        00000100        00010000        00000000 

Pour atteindre le deuxième résultat uniquement, j'ai montré trois étapes hypothétiques ci-dessus.

Veuillez m'expliquer pourquoi les deux résultats ci-dessus sont différents: Le résultat réel de numb << 10est différent du résultat attendu.

Sandeep Singh
la source

Réponses:

194

L'endianité est la façon dont les valeurs sont stockées en mémoire. Lorsqu'elle est chargée dans le processeur, quelle que soit l'endianité, l'instruction de décalage de bits fonctionne sur la valeur dans le registre du processeur. Par conséquent, le chargement de la mémoire vers le processeur équivaut à la conversion en big endian, l'opération de décalage vient ensuite, puis la nouvelle valeur est stockée en mémoire, c'est là que l'ordre des octets du petit boutien entre à nouveau en vigueur.

Mise à jour, grâce à @jww: sur PowerPC, les décalages et les rotations vectorielles sont sensibles aux extrémités. Vous pouvez avoir une valeur dans un registre vectoriel et un décalage produira des résultats différents sur little-endian et big-endian .

Carl
la source
4
Merci pour l'explication. Pouvez-vous s'il vous plaît suggérer une référence où je peux obtenir une meilleure compréhension de ces subtilités.
Sandeep Singh
4
La meilleure chose pour comprendre l'endianness est de vraiment l'utiliser sur différentes architectures à un niveau embarqué. Cependant, je pourrais vous référer à ces deux articles: codeproject.com/KB/cpp/endianness.aspx et ibm.com/developerworks/aix/library/au-endianc/...
Carl
3
Donc mon code fonctionnera quel que soit endian?! c'est bien! J'étais tellement inquiet de devoir pirater mon code en enfer et revenir!
MarcusJ
2
@MarcusJ: Pas nécessairement. Par exemple, si vous lisez 4 octets à partir d'un fichier qui représente un entier de 32 bits, vous devez tenir compte de l'endianité des données que vous lisez conjointement avec l'endianité du système recevant les données afin d'interpréter correctement les données.
Carl
3
Sur PowerPC, les décalages et les rotations vectorielles sont sensibles aux extrémités. Vous pouvez avoir une valeur dans un registre vectoriel et un décalage produira des résultats différents sur little-endian et big-endian.
jww
58

Non, le décalage de bits, comme toute autre partie de C, est défini en termes de valeurs , pas de représentations. Le décalage vers la gauche de 1 est une multiplication par 2, le décalage vers la droite est la division. (Comme toujours lors de l'utilisation d'opérations au niveau du bit, méfiez-vous de la signature. Tout est mieux défini pour les types intégraux non signés.)

Kerrek SB
la source
1
Ceci est fondamentalement vrai pour l'arithmétique entière, mais C fournit de nombreux cas de comportement dépendant de la représentation.
Edmund
2
@Edmund: Hm ... plus particulièrement l'implémentation de la signature n'est pas spécifiée, et par conséquent le comportement des opérations au niveau du bit (comme le décalage à droite) et de modulo et divide sont définis sur des entiers négatifs. Quelles autres choses avez-vous à l'esprit qui sont définies par la mise en œuvre?
Kerrek SB
@KerrekSB, malheureusement, leur implémentation n'est pas définie sur des entiers négatifs. Ils ne sont pas spécifiés dans C89 et non définis dans C99 +, ce qui était une très mauvaise idée.
Paolo Bonzini le
@PaoloBonzini: Oui, bon point. En fait, c'est encore mieux, car cela renforce le fait que les opérations de décalage sont définies en termes de valeurs, pouvant être indéfinies lorsque le résultat n'est pas représentable, et que spéculer sur la représentation sous-jacente n'aide pas.
Kerrek SB le
@KerrekSB: le fait est que tout le monde a besoin d'un décalage vers la gauche pour être représenté à la fois en tant que valeurs et en tant que représentation, selon le cas. Et l'utilisation d'entiers non signés peut causer d'autres problèmes, par exemple, x &= -1u << 20sera probablement incorrect si xest 64 bits et int32 bits. Pour cette raison, GCC promet de ne jamais traiter les équipes signées comme non définies ou même non spécifiées.
Paolo Bonzini
5

Quelle que soit l'instruction de décalage qui décale les bits d'ordre supérieur en premier, est considérée comme le décalage à gauche. Quelle que soit l'instruction de décalage qui décale les bits d'ordre inférieur en premier, est considérée comme le décalage à droite. En ce sens, le comportement de >>et <<pour les unsignednombres ne dépendra pas de l'endianité.

Davislor
la source
4

Les ordinateurs n'écrivent pas les chiffres comme nous le faisons. La valeur change simplement. Si vous insistez pour le regarder octet par octet (même si ce n'est pas ainsi que l'ordinateur le fait), vous pourriez dire que sur une machine little-endian, le premier octet se décale vers la gauche, les bits en excès vont dans le deuxième octet, etc.

(À propos, little-endian a plus de sens si vous écrivez les octets verticalement plutôt qu'horizontalement, avec des adresses plus élevées en haut. Ce qui se trouve être la façon dont les diagrammes de carte mémoire sont généralement dessinés.)

Raymond Chen
la source
1

Bien que la réponse acceptée souligne que l'endianess est un concept du point de vue de la mémoire. Mais je ne pense pas que cela réponde directement à la question.

Certaines réponses me disent que les opérations au niveau du bit ne dépendent pas de l'endianess , et le processeur peut représenter les octets de toute autre manière. Quoi qu'il en soit, il parle de cette endianess est abstraite.

Mais lorsque nous faisons des calculs au niveau du bit sur le papier, par exemple, vous n'avez pas besoin de déclarer l'endianess en premier lieu? La plupart du temps, nous choisissons implicitement une fin.

Par exemple, supposons que nous ayons une ligne de code comme celle-ci

0x1F & 0xEF

Comment calculeriez-vous le résultat à la main, sur un papier?

  MSB   0001 1111  LSB
        1110 1111
result: 0000 1111

Nous utilisons donc ici un format Big Endian pour faire le calcul. Vous pouvez également utiliser Little Endian pour calculer et obtenir le même résultat.

Btw, quand nous écrivons des nombres dans le code, je pense que c'est comme un format Big Endian. 123456ou 0x1F, les nombres les plus significatifs commencent par la gauche.

Encore une fois, dès que nous écrivons un format binaire d'une valeur sur le papier, je pense que nous avons déjà choisi une Endianess et nous regardons la valeur telle que nous la voyons dans la mémoire.

Revenons donc à la question, une opération de décalage <<devrait être considérée comme un passage de LSB (octet le moins significatif) à MSB (octet le plus significatif) .

Puis comme pour l'exemple dans la question:

numb=1025

Petit endian

LSB 00000001 00000100 00000000 00000000 MSB

Ce << 10serait donc 10bitpasser du LSB au MSB.


Comparaison et << 10opérations pour le format Little Endian étape par étape:

MSB                                        LSB
    00000000  00000000  00000100  00000001  numb(1025)
    00000000  00010000  00000100  00000000  << 10

LSB                                        MSB
    00000000  00000100  00010000  00000000 numb(1025) << 10, and put in a Little Endian Format

LSB                                        MSB
    00000001  00000100  00000000  00000000 numb(1205) in Little Endian format
    00000010  00001000  00000000  00000000 << 1 
    00000100  00010000  00000000  00000000 << 2 
    00001000  00100000  00000000  00000000 << 3 
    00010000  01000000  00000000  00000000 << 4
    00100000  10000000  00000000  00000000 << 5
    01000000  00000000  00000001  00000000 << 6
    10000000  00000000  00000010  00000000 << 7
    00000000  00000001  00000100  00000000 << 8
    00000000  00000010  00001000  00000000 << 9
    00000000  00000100  00010000  00000000 << 10 (check this final result!)

Hou la la! J'obtiens le résultat attendu comme décrit par l'OP!

Les problèmes que l'OP n'a pas obtenu le résultat attendu sont les suivants:

  1. Il semble qu'il ne soit pas passé du LSB au MSB.

  2. Lorsque vous déplacez des bits au format Little Endian, vous devez vous rendre compte (dieu merci, je le réalise) que:

LSB 10000000 00000000 MSB << 1est
LSB 00000000 00000001 MSB, pas LSB 01000000 00000000 MSB

Parce que pour chaque individu 8bits, nous l'écrivons en fait dans un MSB 00000000 LSBformat Big Endian.

Alors c'est comme

LSB[ (MSB 10000000 LSB) (MSB 00000000 LSB) ]MSB


Pour résumer:

  1. Bien que l'on dit que les opérations au niveau du bit sont abstraites blablablabla ..., lorsque nous calculons les opérations au niveau du bit à la main, nous avons encore besoin de savoir quelle extrémité nous utilisons lorsque nous écrivons le format binaire sur le papier. Nous devons également nous assurer que tous les opérateurs utilisent la même finalité.

  2. L'OP n'a pas obtenu le résultat escompté parce qu'il a mal agi.

Meule
la source