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

Почему не осталось бит-сдвиг, "<<", для 32-битных целых чисел работают как ожидалось при использовании более 32 раз?

Когда я пишу следующую программу и использую компилятор GNU С++, вывод 1, который, по моему мнению, обусловлен операцией вращения, выполняемой компилятором.

#include <iostream>

int main()
{
    int a = 1;
    std::cout << (a << 32) << std::endl;

    return 0;
}

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

Код находится на ideone, http://ideone.com/VPTwj.

4b9b3361

Ответ 1

Это вызвано сочетанием поведения undefined в C и факта, что код, созданный для процессоров IA-32, имеет 5-битную маску, применяемую к счету сдвига. Это означает, что на процессорах IA-32 диапазон смещения считается 0-31. 1

Из языка программирования C 2

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

Из IA-32 Руководство разработчика программного обеспечения Intel Architecture 3

8086 не маскирует количество сдвигов. Однако все остальные процессоры IA-32 (начиная с процессора Intel 286) маскируют счетчик сдвига до 5 бит, что приводит к максимальному счету 31. Эта маскировка выполняется во всех режимах работы (включая режим виртуального 8086) до уменьшите максимальное время выполнения инструкций.



1http://codeyarns.com/2004/12/20/c-shift-operator-mayhem/

2 A7.8 Операторы сдвига, Приложение A. Справочное руководство, Язык программирования C

3 SAL/SAR/SHL/SHR - Shift, глава 4. Справочник по набору инструкций, Руководство разработчика программного обеспечения Intel по архитектуре IA-32

Ответ 2

В С++ сдвиг только четко определен, если вы смещаете значение меньше шагов, чем размер типа. Если int - 32 бита, то корректно определяется только 0 до 31 и включает в себя 31 шаг.

Итак, почему это?

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

Ответ на вопрос в комментарии

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

Ответ 3

Это поведение undefined в соответствии со стандартом С++:

Значение E1 < E2 - E1 сдвинутые слева позиции E2; освобождено бит заполнены нулями. Если E1 имеет неподписанный тип, значение результата E1 × 2E ^ 2, приведено по модулю еще один чем максимальное значение, представляемое в типе результата. В противном случае, если E1 имеет подписанный тип и неотрицательный значение и E1 × 2E ^ 2 представимо в тип результата, то это итоговое значение; в противном случае поведение undefined.

Ответ 4

Ответы Lindydancer и 6502 объясняют, почему (на некоторых машинах) печатается 1 (хотя поведение операции undefined). Я добавляю детали, если они не очевидны.

Я предполагаю, что (как и я) вы запускаете программу на процессоре Intel. GCC генерирует эти инструкции по сборке для операции переключения:

movl $32, %ecx
sall %cl, %eax

В разделе sall и других операциях смены на стр. 624 в Справочное руководство по набору инструкций говорится:

8086 не маскирует количество сдвигов. Однако все остальные процессоры Intel Architecture (начиная с процессора Intel 286) маскируют счетчик сдвига до пяти бит, в результате чего максимальное значение 31. Это маскирование выполняется во всех режимах работы (включая виртуальный 8086 mode), чтобы уменьшить максимальное время выполнения инструкций.

Так как нижние 5 бит 32 равны нулю, то 1 << 32 эквивалентно 1 << 0, который равен 1.

Экспериментируя с большими числами, мы прогнозируем, что

cout << (a << 32) << " " << (a << 33) << " " << (a << 34) << "\n";

будет печатать 1 2 4, и действительно, это то, что происходит на моей машине.

Ответ 5

Это не работает так, как ожидалось, потому что вы слишком много ожидаете.

В случае x86 аппаратное обеспечение не заботится о операциях смены, где счетчик больше размера регистра (см., например, описание команды SHL в справочной документации x86 для объяснения).

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

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

Более "полезным" и "логическим" подходом было бы, например, иметь (x << y) эквивалент (x >> -y), а также обработку высоких счетчиков с логическим и последовательным поведением.

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

Учитывая, что в этих случаях разные аппаратные средства делают разные вещи, что стандарт говорит в основном: "Что бы ни случилось, когда вы делаете странные вещи, просто не вините С++, это ваша вина" переведена на законную.

Ответ 6

Смещение 32-битной переменной на 32 или более бит - это поведение undefined и может заставить компилятор заставить демонов вылететь из вашего носа.

Серьезно, большую часть времени вывод будет 0 (если int равно 32 бит или меньше), так как вы перемещаете 1 до тех пор, пока он не упадет снова, и останется только 0. Но компилятор может оптимизировать его, чтобы делать все, что ему нравится.

См. замечательную запись в блоге LLVM Что должен знать каждый программист C о undefined Поведение, обязательно читать для каждого разработчика C.

Ответ 7

Поскольку вы смещаете бит int на 32 бита; вы получите: warning C4293: '<<' : shift count negative or too big, undefined behavior в VS. Это означает, что вы переходите за пределы целого числа, и ответ может быть НИЧЕГО, потому что это поведение undefined.

Ответ 8

Вы можете попробовать следующее. Это фактически дает результат как 0 после 32 сдвигов влево.

#include<iostream>
#include<cstdio>

using namespace std;

int main()
{
  int a = 1;
  a <<= 31;
  cout << (a <<= 1);
  return 0;
}

Ответ 9

У меня была та же проблема, и это сработало для меня:

f = ((длинный длинный) 1 < (i-1));

Где я могу быть целым числом больше 32 бит. 1 должен быть 64-битным целым для перехода на работу.