Почему не левый бит-сдвиг, "


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

#include <iostream>

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

    return 0;
}

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

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

9 53

9 ответов:

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

с язык программирования C2

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

с руководство разработчика программного обеспечения архитектуры Intel IA-323

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 – Shift, Глава 4. Справочник по набору инструкций, IA-32 Intel Architecture Software Developer's Manual

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

Так почему же это?

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

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

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

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

значением Е1 в противном случае поведение не определено.

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

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

movl , %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 Должен Знать О Undefined Поведение, обязательное чтение для каждого разработчика 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-битным целым числом для переключения на работу.