Странное поведение оператора сдвига вправо (1>> 32)

Я недавно столкнулся со странным поведением с помощью оператора сдвига вправо.

Следующая программа:

#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <stdint.h>

int foo(int a, int b)
{
   return a >> b;
}

int bar(uint64_t a, int b)
{
   return a >> b;
}

int main(int argc, char** argv)
{
    std::cout << "foo(1, 32): " << foo(1, 32) << std::endl;
    std::cout << "bar(1, 32): " << bar(1, 32) << std::endl;
    std::cout << "1 >> 32: " << (1 >> 32) << std::endl; //warning here
    std::cout << "(int)1 >> (int)32: " << ((int)1 >> (int)32) << std::endl; //warning here

    return EXIT_SUCCESS;
}

Выводы:

foo(1, 32): 1 // Should be 0 (but I guess I'm missing something)
bar(1, 32): 0
1 >> 32: 0
(int)1 >> (int)32: 0

Что происходит с foo() функция? Я понимаю, что единственная разница между тем, что она делает и последние 2 строки, то, что последние две строки оценены во время компиляции. И почему это "работает", если я использую целое число на 64 бита?

Любые световые сигналы относительно этого будут значительно цениться!


Конечно, связанный, вот что g++ дает:

> g++ -o test test.cpp
test.cpp: In function 'int main(int, char**)':
test.cpp:20:36: warning: right shift count >= width of type
test.cpp:21:56: warning: right shift count >= width of type
21
задан Gilles 'SO- stop being evil' 16 October 2017 в 12:21
поделиться

7 ответов

Вероятно, ЦП фактически вычисляет

a >> (b % 32)

в foo ; между тем, 1 >> 32 является константным выражением, поэтому компилятор свернет константу во время компиляции, что каким-то образом даст 0.

Поскольку стандарт (C ++ 98 §5.8 / 1) ) утверждает, что

Поведение не определено , если правый операнд отрицательный, или больше или равен длине в битах продвинутого левого операнда.

нет противоречия, если foo (1,32) и 1 >> 32 дают разные результаты.

С другой стороны, в bar вы указали 64-битное значение без знака, так как 64> 32 гарантируется, что результат должен быть 1/2 32 = 0. Тем не менее, если вы напишете

bar(1, 64);

, вы все равно можете получить 1.


Изменить: логический сдвиг вправо (SHR) ведет себя как a >> (b% 32/64) на x86 / x86-64 (Intel # 253667, стр. 4-404):

Операнд назначения может быть регистром или ячейкой памяти. Операнд счетчика может быть непосредственным значением или регистром CL. Счетчик маскируется до 5 бит (или до 6 бит в 64-битном режиме и используется REX.W). Диапазон счета ограничен значением от 0 до 31 (или 63, если используется 64-битный режим и REX.W).Специальная кодировка кода операции предусмотрена для счетчика 1.

Однако в ARM (по крайней мере, armv6 и 7) логический сдвиг вправо (LSR) реализован как (ARMISA Страница A2-6)

(bits(N), bit) LSR_C(bits(N) x, integer shift)
    assert shift > 0;
    extended_x = ZeroExtend(x, shift+N);
    result = extended_x<shift+N-1:shift>;
    carry_out = extended_x<shift-1>;
    return (result, carry_out);

где (ARMISA Page AppxB-13)

ZeroExtend(x,i) = Replicate('0', i-Len(x)) : x

Это гарантирует, что сдвиг вправо на ≥32 приведет к нулю. Например, когда этот код запускается на iPhone, foo (1,32) даст 0.

Здесь показано, что сдвиг 32-битного целого числа на ≥32 непереносим.

35
ответ дан 29 November 2019 в 20:39
поделиться

В foo происходит то, что ширина сдвига больше или равна размеру сдвигаемых данных. В стандарте C99 это приводит к неопределенному поведению. Вероятно, то же самое происходит в любом стандарте C++, под который создан MS VC++.

Причина этого в том, чтобы позволить разработчикам компиляторов использовать преимущества любой аппаратной поддержки сдвигов в процессоре. Например, в архитектуре i386 есть инструкция для сдвига 32-битного слова на некоторое количество битов, но количество битов определяется в поле инструкции шириной 5 битов. Скорее всего, ваш компилятор генерирует инструкцию, беря сумму битового сдвига и маскируя ее 0x1F, чтобы получить битовый сдвиг в инструкции. Это означает, что сдвиг на 32 равен сдвигу на 0.

.
3
ответ дан 29 November 2019 в 20:39
поделиться

Я полагаю, причина в том, что тип int содержит 32 бита (для большинства систем), но один бит используется для знака, поскольку это тип со знаком. Таким образом, для фактического значения используется только 31 бит.

0
ответ дан 29 November 2019 в 20:39
поделиться

Я скомпилировал его на 32-битной системе windows с помощью компилятора VC9. Он выдал мне следующее предупреждение. Поскольку sizeof(int) в моей системе составляет 4 байта, компилятор указывает, что сдвиг вправо на 32 бита приводит к неопределенному поведению. Поскольку оно не определено, вы не можете предсказать результат. Просто для проверки я сдвинул вправо на 31 бит, и все предупреждения исчезли, а результат также был ожидаемым (т.е. 0).

1
ответ дан 29 November 2019 в 20:39
поделиться

foo (1,32) выполняет вращение, поэтому биты, которые должны исчезнуть справа, снова появляются слева. Если вы сделаете это 32 раза, единичный бит, установленный в 1, вернется в исходное положение.

bar (1,32) то же самое, но бит находится в 64-32 + 1 = 33-м бите, что выше представимых чисел для 32-битного int. Берутся только 32 младших бита, и все они равны нулю.

1 >> 32 выполняется компилятором. Понятия не имею, почему gcc использует невращающийся сдвиг здесь, а не в сгенерированном коде.

То же самое для ((int) 1 >> (int) 32)

-5
ответ дан 29 November 2019 в 20:39
поделиться

Предупреждение говорит само за себя!

Но, справедливости ради, меня однажды укусила та же ошибка.

int a = 1;
cout << ( a >> 32);

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

0
ответ дан 29 November 2019 в 20:39
поделиться

Хорошо. Так и в 5.8.1:

Операнды должны быть целочисленного или перечислимого типа, и выполняться интегральные продвижения. Тип результата что из продвинутого левого операнда. Поведение не определено, если правый операнд отрицательный, больше или равен длина в битах продвинутого левого операнда.

Итак, у вас неопределенное поведение (тм).

6
ответ дан 29 November 2019 в 20:39
поделиться