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



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



#include <iostream>

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

return 0;
}


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

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

621   9  

9 ответов:

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

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

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

Из руководства разработчика по архитектуре Intel IA-32 3

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



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

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

3 Sal/SAR/SHL / SHR – сдвиг, Глава 4. Справочник по набору инструкций, IA-32 Intel Architecture Software Developer's Manual

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

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

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

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

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

Это неопределенное поведение в соответствии со стандартом C++:

Значение E1 в противном случае, то поведение не определено .

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

Я предполагаю, что (как и я) вы запускаете программу на процессоре Intel. GCC генерирует следующие инструкции по сборке для операции сдвига:
movl $32, %ecx
sall %cl, %eax

По теме sall и других операций сдвига, страница 624 в наборе инструкций Справочное руководство гласит:

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

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

Экспериментируя с большими числами, мы могли бы предсказать, что
cout << (a << 32) << " " << (a << 33) << " " << (a << 34) << "\n";

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

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

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

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

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

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

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

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

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

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

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

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

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

#include<iostream>
#include<cstdio>

using namespace std;

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

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

F = ((long long) 1

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

Comments

    Ничего не найдено.