Бит-сдвиг зависит от порядка байтов?


155

Предположим, у меня есть 'numb'=1025 [00000000 00000000 00000100 00000001]представленное число :

На Little-Endian Machine:

00000001 00000100 00000000 00000000

На Big-Endian Machine:

00000000 00000000 00000100 00000001

Теперь, если я применю сдвиг влево на 10 битов (то есть: numb << = 10), я должен иметь:

[A] На Little-Endian Machine:

Как я заметил в GDB, Little Endian выполняет сдвиг влево в 3 этапа: [Я показал «3» шага, чтобы лучше понять только обработку]

  1. Лечи нет. в Big-Endian Convention:

    00000000        00000000        00000100    00000001
  2. Применить сдвиг влево:

    00000000        00010000        00000100        00000000
  3. Представьте Результат снова в Little-Endian:

    00000000        00000100        00010000        00000000 

[B]. На Big-Endian Machine:

00000000        00010000        00000100        00000000

Мой вопрос:

Если я непосредственно применяю сдвиг влево к конвенции Little Endian, это должно дать:

numb:

00000001 00000100 00000000 00000000

numb << 10:

00010000 00000000 00000000 00000000

Но на самом деле это дает:

00000000        00000100        00010000        00000000 

Чтобы достичь только второго результата, я показал три гипотетических шага выше.

Пожалуйста, объясните мне, почему вышеупомянутые два результата отличаются: Фактический результат numb << 10отличается от ожидаемого результата.

Ответы:


194

Endianness - это способ хранения значений в памяти. При загрузке в процессор, независимо от порядкового номера, команда сдвига битов работает со значением в регистре процессора. Следовательно, загрузка из памяти в процессор является эквивалентом преобразования в старший порядок байтов, затем следует операция сдвига, а затем новое значение сохраняется в памяти, в которой порядок байтов с младшим порядком байтов снова вступает в силу.

Обновление, благодаря @jww: На PowerPC сдвиги и повороты вектора чувствительны к порядку байтов. Вы можете иметь значение в векторном регистре, и сдвиг будет приводить к разным результатам в младшем и старшем порядке .


4
Спасибо за объяснение. Можете ли вы предложить некоторые ссылки, где я могу получить лучшее понимание таких сложностей.
Сандип Сингх

4
Лучшая вещь для понимания порядка байтов - это использовать его на разных архитектурах на встроенном уровне. Тем не менее, я мог бы отослать вас к этим двум статьям: codeproject.com/KB/cpp/endianness.aspx и ibm.com/developerworks/aix/library/au-endianc/…
Карл

3
Так что мой код будет работать независимо от порядка байтов ?! это круто! Я так волновался, что мне придется взломать мой код в ад и обратно!
MarcusJ

2
@MarcusJ: не обязательно. Например, если вы читаете 4 байта из файла, представляющего 32-разрядное целое число, вам необходимо учитывать порядковый номер данных, которые вы читаете, в сочетании с порядковым номером системы, получающей данные, чтобы правильно интерпретировать данные.
Карл

3
На PowerPC сдвиги и повороты вектора чувствительны к порядку байтов. Вы можете иметь значение в векторном регистре, и сдвиг будет приводить к разным результатам в младшем и старшем порядковом порядке.
августа

58

Нет, битовое смещение, как и любая другая часть C, определяется в терминах значений , а не представлений. Сдвиг влево на 1 - это умножение на 2, сдвиг вправо - это деление. (Как всегда при использовании побитовых операций, остерегайтесь подписи. Все наиболее четко определено для целых типов без знака.)


1
Это в основном верно для целочисленной арифметики, но C предоставляет множество случаев поведения, зависящего от представления.
Эдмунд

2
@Edmund: Хм ... в первую очередь реализация подписи не указана, и, как следствие, поведение побитовых операций (таких как сдвиг вправо), а также модуля и деления - это реализация, определенная для отрицательных целых чисел. Какие еще вещи вы имеете в виду, которые определяются реализацией?
Kerrek SB

@KerrekSB, к сожалению, они не определены реализацией для отрицательных целых чисел. Они не определены в C89 и не определены в C99 +, что было очень плохой идеей.
Паоло Бонзини

@PaoloBonzini: Да, хорошая мысль. На самом деле это даже лучше, поскольку это подтверждает тот факт, что операции сдвига определяются в терминах значений, возможно, они не определены, когда результат не может быть представлен, и что размышления о базовом представлении не помогают.
Kerrek SB

@KerrekSB: дело в том, что каждому нужно, чтобы левый сдвиг был представлен как значения и как представление, в зависимости от случая. А использование целых чисел без знака может вызвать другие проблемы, например x &= -1u << 20, наиболее вероятно, будет неправильным, если xон 64-битный и int32-битный. По этой причине GCC обещает никогда не рассматривать подписанные смены как неопределенные или даже неуказанные.
Паоло Бонзини

5

Независимо от того, какая команда сдвига сдвигает биты старшего разряда в первую очередь, считается левым сдвигом. Какая бы команда сдвига ни сдвинула младшие биты первыми, считается правильным сдвигом. В этом смысле поведение >>и <<для unsignedчисел не будет зависеть от порядка байтов.


4

Компьютеры не записывают цифры так, как мы. Значение просто меняется. Если вы настаиваете на том, чтобы смотреть на него побайтово (хотя компьютер этого не делает), вы могли бы сказать, что на машине с прямым порядком байтов первый байт сдвигается влево, избыточные биты переходят во второй байт, и так далее.

(Кстати, little-endian имеет больше смысла, если вы пишете байты по вертикали, а не по горизонтали, с более высокими адресами сверху. Именно так обычно и выглядят диаграммы карты памяти.)


1

Хотя принятый ответ указывает на то, что бесконечность является понятием с точки зрения памяти. Но я не думаю, что это ответит на вопрос напрямую.

Некоторые ответы говорят мне, что побитовые операции не зависят от порядка байтов , и процессор может представлять байты любым другим способом. Во всяком случае, речь идет об этой абстракции.

Но когда мы делаем некоторые побитовые вычисления, например, на бумаге, не нужно ли указывать порядковый номер в первую очередь? В большинстве случаев мы выбираем порядок байтов неявно.

Например, предположим, что у нас есть строка кода, подобная этой

0x1F & 0xEF

Как бы вы рассчитали результат вручную, на бумаге?

  MSB   0001 1111  LSB
        1110 1111
result: 0000 1111

Поэтому здесь мы используем формат Big Endian, чтобы сделать расчет. Вы также можете использовать Little Endian для расчета и получения того же результата.

Кстати, когда мы пишем числа в коде, я думаю, что это формат Big Endian. 123456или0x1F наиболее значимые цифры начинаются слева.

Опять же, как только мы напишем какой-нибудь двоичный формат значения на бумаге, я думаю, что мы уже выбрали Endianess, и мы рассматриваем значение так, как мы видим его из памяти.

Итак, возвращаясь к вопросу, операция сдвига <<должна рассматриваться как смещение от младшего бита (младший байт) к старшему биту (старший байт) .

Тогда как для примера в вопросе:

numb=1025

Little Endian

LSB 00000001 00000100 00000000 00000000 MSB

Так << 10будет 10bitпереход от LSB к MSB.


Сравнение и << 10операции для формата Little Endian шаг за шагом:

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!)

Вот Это Да! Я получаю ожидаемый результат, как описано в ОП!

Проблемы, которые ОП не получил ожидаемый результат, заключаются в том, что:

  1. Кажется, он не перешел от LSB к MSB.

  2. При перемещении битов в формате Little Endian вы должны понимать (слава богу, я это понимаю), что:

LSB 10000000 00000000 MSB << 1это
LSB 00000000 00000001 MSB, не LSB 01000000 00000000 MSB

Потому что для каждого человека 8bitsмы пишем его в формате MSB 00000000 LSBBig Endian.

Так это как

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


Подводить итоги:

  1. Хотя побитовые операции называются абстрагированными, блаблабла ..., когда мы вычисляем побитовые операции вручную, нам все равно нужно знать, какую последовательность мы используем, записывая двоичный формат на бумаге. Также нам нужно убедиться, что все операторы используют один и тот же порядок байтов.

  2. ОП не получил ожидаемый результат, потому что он сделал сдвиг неправильно.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.