Почему сдвиг вправо на 64 бита является тождеством, а не нильпотентным (возвращает 0)?

Этот код говорит сам за себя, но я не могу опубликовать вопрос, пока не скажу что-нибудь здесь!

#include <stdio.h>
#include <limits.h>

void rightshift_test(int const shift_amount) {
  unsigned long a = 0xabcd1234abcd1234UL;
  printf("a: %lx, shift_amount:%d\n", a, shift_amount);
  unsigned long const b = a >> shift_amount;
  printf("b: %lx\n", b);
}

int main() {
  rightshift_test(56);
  rightshift_test(60);
  rightshift_test(61);
  rightshift_test(62);
  rightshift_test(63);
  rightshift_test(64);
  return 0;
}

И это все еще не позволяет мне опубликовать вопрос. Здесь работает код:

gcc -Wall -Wextra -Werror foo.c -o foo
./foo
a: abcd1234abcd1234, shift_amount:56
b: ab
a: abcd1234abcd1234, shift_amount:60
b: a
a: abcd1234abcd1234, shift_amount:61
b: 5
a: abcd1234abcd1234, shift_amount:62
b: 2
a: abcd1234abcd1234, shift_amount:63
b: 1
a: abcd1234abcd1234, shift_amount:64
b: abcd1234abcd1234

А вот подробности моего компилятора и моей машины, если это не очень очевидно.

$ gcc --version
i686-apple-darwin10-gcc-4.2.1 (GCC) 4.2.1 (Apple Inc. build 5666) (dot 3)

$ echo $MACHTYPE
x86_64-apple-darwin10.0

person Daniel    schedule 06.07.2012    source источник
comment
Сдвиг более чем на размер типа данных является неопределенным поведением. Конец истории.   -  person Mysticial    schedule 06.07.2012
comment
Чтобы добавить к комментарию Mystical выше: полный циклический сдвиг, вероятно, (неправильно) оптимизирован как NOP.   -  person Germann Arlington    schedule 06.07.2012
comment
Чтобы добавить к этому комментарию, это не неправильная оптимизация, потому что все правильно, когда программа вызывает UB :-) На некоторых аппаратных средствах инструкции сдвига фактически маскируют операнд с 0x3F, потому что они смотрят только на младшие биты. Я не знаю, делает ли это x86-64, но, естественно, в результате >>64 имеет тот же эффект, что и >>0.   -  person Steve Jessop    schedule 06.07.2012
comment
@SteveJessop x86-64 делает это, да. Маска зависит от размера операнда.   -  person harold    schedule 07.07.2012
comment
Как указано выше, операции сдвига/поворота Intel маскируют величину сдвига размером операнда, поэтому сдвиг на 64 приведет к смещению на 0. В ARM это не так, и результат сдвига, превышающий размер операнда, будет выводиться 0.   -  person BitBank    schedule 12.07.2012
comment
Я попробовал код на своем Mac x86_64. Подтверждая вышеизложенное, 64-битный сдвиг дал 0xabcd1234abcd1234, 65-битный дал 0x55e6891a55e6891a, а 66-битный дал 0x2af3448d2af3448d.   -  person Steve Summit    schedule 19.06.2021


Ответы (1)


В стандарте ISO C99 есть небольшой фрагмент, касающийся операторов битового сдвига:

shift-expression:
additive-expression
shift-expression << additive-expression
shift-expression >> additive-expression

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

Другими словами, как только вы начнете пытаться сдвинуть 64-битное (в вашей реализации) unsigned long на 64 бита или более, все ставки будут сняты. Он может возвращать 0, исходное значение или 42. Он даже может форматировать ваш жесткий диск и отправлять порнографии вашему боссу (хотя я никогда не видел, чтобы реализация действительно делала это).

Вы можете обнаружить, что некоторые реализации (или даже базовое аппаратное обеспечение реализации) оптимизируют эти инструкции, эффективно используя только соответствующие биты. Другими словами, для 64-битного значения он вполне может использовать только младшие значащие семь бит (фактически and с 0x3f), чтобы дать вам операнд от 0 до 63.

В этом случае, поскольку 64 & 0x3f равно нулю, это фактически не работает.

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

unsigned long const b = a >> shift_amount;

во что-то вроде:

unsigned long const b = (shift_amount < 64) ? a >> shift_amount : 0;

как показано в этом коде (64-битные значения в моей системе являются ULL):

#include <stdio.h>
#include <limits.h>

void rightshift_test(int const shift_amount) {
    unsigned long long a = 0xabcd1234abcd1234ULL;
    printf("a: %llx, shift_amount:%d: ", a, shift_amount);
    unsigned long long const b = (shift_amount < 64) ? a >> shift_amount : 0;
    printf("b: %llx\n", b);
}

int main() {
    rightshift_test(56);
    rightshift_test(60);
    rightshift_test(61);
    rightshift_test(62);
    rightshift_test(63);
    rightshift_test(64);
    rightshift_test(99);
    return 0;
}

Результат этого:

a: abcd1234abcd1234, shift_amount:56: b: ab
a: abcd1234abcd1234, shift_amount:60: b: a
a: abcd1234abcd1234, shift_amount:61: b: 5
a: abcd1234abcd1234, shift_amount:62: b: 2
a: abcd1234abcd1234, shift_amount:63: b: 1
a: abcd1234abcd1234, shift_amount:64: b: 0
a: abcd1234abcd1234, shift_amount:99: b: 0
person paxdiablo    schedule 13.09.2012