Подтвердить что ты не робот

Арифметический сдвиг вправо дает фиктивный результат?

Я должен быть абсолютно сумасшедшим здесь, но gcc 4.7.3 на моей машине дает самый абсурдный результат. Вот точный код, который я тестирую:

#include <iostream>

using namespace std;

int main(){
  unsigned int b = 100000;
  cout << (b>>b) << endl;
  b = b >> b;
  cout << b << endl;
  b >>= b;
  cout << b << endl;
  return 0;
}

Теперь любое число, которое сдвинуто по собственному праву, должно привести к 0 (n/(2^n) == 0 с целым делением, n>1 и положительным/неподписанным), но каким-то образом это мой вывод:

100000
100000
100000

Я сошел с ума? Что может происходить?

4b9b3361

Ответ 1

В С++, как и в C, сдвиги ограничены размером (в битах) сдвинутого значения. Например, если unsigned int - 32 бита, тогда сдвиг более 31 равен undefined.

На практике общий результат состоит в том, что используются 5 наименее значимых бит величины сдвига, а биты более высокого порядка игнорируются; это связано с тем, что компилятор создает машинную инструкцию, которая выполняет именно это (например, SHR на x86).

В этом случае значение сдвига 100000 (десятичное), которое в двоичном выражении 11000011010100000 - младшие 5 бит равны нулю. Таким образом, вы эффективно получаете сдвиг на 0. Однако вы не должны полагаться на это; технически то, что вы видите, - это поведение undefined.

Литература:

Для C, N1570 раздел 6.5.7:

Если значение правого операнда отрицательное или больше или равно равной ширине продвинутого левого операнда, поведение undefined.

Для С++ N3690 раздел 5.8 "[expr.shift]":

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

N1570 - это проект, почти идентичный выпуску стандарта ISO C11; этот раздел был почти таким же, как и в стандарте ANSI C. 1989 года.

N3690 - это недавний проект стандарта С++; Я не уверен, что это лучший вариант для использования, но опять же это предложение не изменилось.

Ответ 2

Вы вызываете undefined поведение, если вы смещаете больше длины бит левого операнда, черновик С++ стандартный раздел 5.8 Операторы сдвига в абзаце 1 говорят (внимание мое):

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

Интересно отметить, что оба gcc и clang могут генерировать предупреждение для этого кода, если значение сдвига, если литерал:

cout << (b>> 100000) ;

или если b является константой, предупреждение для gcc выглядит следующим образом:

warning: right shift count >= width of type [enabled by default]

как указывает MSalters в комментариях к вопросу, мы не можем даже полагаться на это предупреждение, поскольку это поведение undefined, которое согласуется с примечанием о стандартах на поведение undefined в терминах и определениях в котором говорится:

Примечание. [...] Допустимые диапазоны поведения undefined: от полного игнорирования ситуации с непредсказуемыми результатами, поведения во время перевода или выполнения программы в документально оформленной манере, характерной для среды (с выдачей диагностического сообщения или без него), до прекращения перевода или выполнения (с выдачей диагностического сообщения). [...]

Детали, специфичные для платформы

Потенциальное объяснение очевидного отсутствия сдвига в примере кода может быть связано с тем, что на некоторых платформах количество сдвигов будет замаскировано на 5 bits, например, на архитектуре x86, мы можем видеть Руководство разработчика программного обеспечения Intel® 64 и IA-32 раздел SAL/SAR/SHL/SHR-Shift в разделе совместимости архитектуры IA-32 гласит:

8086 не маскирует количество сдвигов. Тем не менее, все другие процессоры IA-32 (начиная с процессора Intel 286) маскируют счетчик сдвига до 5 бит, что приводит к максимальному числу 31. [...]