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

Почему операция сдвига влево вызывает Undefined Поведение, когда левый операнд имеет отрицательное значение?

В C побитовая операция сдвига влево вызывает Undefined Поведение, когда левый операнд имеет отрицательное значение.

Соответствующая цитата из ISO C99 (6.5.7/4)

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

Но в С++ поведение корректно определено.

ISO С++ - 03 (5.8/2)

Значение E1 < E2 - E1 (интерпретируется как битовый шаблон), сдвинутые слева позиции E2; освобожденные биты заполняются нулями. Если E1 имеет неподписанный тип, значение результата E1 умножается на величину 2, поднятую до мощности E2, уменьшенную по модулю ULONG_MAX + 1, если E1 имеет тип unsigned long, UINT_MAX + 1 в противном случае. [Примечание: константы ULONG_MAX и UINT_MAXare определены в заголовке). ]

Это означает

int a = -1, b=2, c;
c= a << b ;

вызывает Undefined Поведение в C, но поведение хорошо определено в С++.

Что заставило комитет ISO С++ учесть, что поведение хорошо определено в отличие от поведения в C?

С другой стороны, поведение implementation defined для операции побитового правого сдвига, когда левый операнд отрицательный, справа?

Мой вопрос в том, почему операция сдвига влево вызывает Undefined Behavior in C и почему оператор shift shift вызывает только поведение, определенное реализацией?

P.S: Пожалуйста, не давайте ответов, например "Это поведение Undefined, потому что стандарт говорит так".: P

4b9b3361

Ответ 1

Скопированный вами абзац говорит о неподписанных типах. Поведение is не определено в C++. Из последнего C++ 0x черновика:

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

ОБНОВЛЕНИЕ: взглянул на C++ 98 бумаги. Это просто не упоминает подписанные типы вообще. Так что до сих пор неопределенное поведение.

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

Ответ 2

В C побитовая операция сдвига влево вызывает Undefined Поведение, когда левый операнд имеет отрицательное значение. [...] Но в С++ поведение хорошо определено. [...] почему [...]

Легкий ответ: так, как говорят стандарты.

Более длинный ответ: вероятно, это связано с тем, что C и С++ допускают другие представления для отрицательных чисел, кроме 2 дополнений. Предоставление меньших гарантий того, что произойдет, позволяет использовать языки на другом оборудовании, включая неясные и/или старые машины.

По какой-то причине комитету по стандартизации С++ было похоже на добавление небольшой гарантии того, как изменяется представление битов. Но поскольку отрицательные числа все еще могут быть представлены через 1 дополнение или знак + величину, результирующие возможности значения все еще меняются.

Предполагая 16-битные ints, у нас будет

 -1 = 1111111111111111  // 2 complement
 -1 = 1111111111111110  // 1 complement
 -1 = 1000000000000001  // sign+magnitude

Сдвиг влево на 3, мы получим

 -8 = 1111111111111000  // 2 complement
-15 = 1111111111110000  // 1 complement
  8 = 0000000000001000  // sign+magnitude

Что заставило комитет ISO С++ правильно рассмотреть это поведение определяется в отличие от поведения в C?

Я предполагаю, что они сделали эту гарантию, чтобы вы могли использовать < < когда вы знаете, что делаете (т.е. когда вы уверены, что ваша машина использует 2 дополнения).

С другой стороны, поведение - это реализация, определенная для поразрядного правый сдвиг, когда левый операнд отрицательный, правый?

Мне нужно будет проверить стандарт. Но вы можете быть правы. Правая смена без расширения знака на машине с двумя дополнениями не особенно полезна. Таким образом, текущее состояние, безусловно, лучше, чем требовать, чтобы освобожденные биты были заполнены нулями, поскольку он оставляет место машинам, которые делают расширения знака, - хотя это не гарантируется.

Ответ 3

Чтобы ответить на ваш реальный вопрос, как указано в названии: как для любой операции над подписанным типом, это имеет поведение undefined, если результат математической операции не подходит для целевого типа (недо- или переполнение), Подписанные целые типы создаются таким образом.

Для операции сдвига влево, если значение положительное или 0, определение оператора как умножения с степенью 2 имеет смысл, поэтому все в порядке, если только результат не переполняется, ничего удивительного.

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

Мой вывод:

  • если вы хотите сделать реальную битовую диаграмму операции используют неподписанные типы
  • если вы хотите умножить значение (подписанное или нет) силой два, сделайте просто что-то вроде

    i * (1u < k)

ваш компилятор в любом случае преобразует его в достойный ассемблер.

Ответ 4

Многие из этих вещей - это баланс между тем, что обычные процессоры могут действительно поддерживать в одной инструкции, и что достаточно полезно, чтобы ожидать, что разработчики компилятора гарантируют, даже если это потребует дополнительных инструкций. Как правило, программист, использующий операторы бит-сдвига, ожидает, что они будут сопоставлять единые инструкции с процессорами с такими инструкциями, поэтому почему undefined или поведение реализации, когда процессоры имели различную обработку "краевых" условий, вместо того, чтобы задавать поведение и иметь операция будет неожиданно медленной. Имейте в виду, что дополнительные инструкции до/после или обработки могут быть сделаны даже для более простых случаев использования. Возможно, было необходимо поведение undefined, когда некоторые процессоры генерировали ловушки/исключения/прерывания (в отличие от исключений типа try/catch типа С++) или вообще бесполезные/необъяснимые результаты, тогда как если набор процессоров, рассмотренных Комитетом по стандартам на тот момент, все при условии хотя бы некоторого определенного поведения, тогда они могут сделать реализацию реализации определенной.

Ответ 5

Мой вопрос в том, почему операция сдвига влево вызывает Undefined Behavior in C и почему оператор shift shift вызывает только поведение, определенное реализацией?

Люди в LLVM предполагают, что оператор сдвига имеет ограничения из-за того, как инструкция реализована на разных платформах. Из Что должен знать каждый программист C Undefined Поведение № 1/3:

... Я предполагаю, что это произошло потому, что основные операции сдвига на разных ЦП делают с этим разные вещи: например, X86 обрезает 32-битное значение сдвига до 5 бит (так что сдвиг на 32 бита одинаковый как сдвиг на 0 бит), но PowerPC усекает 32-разрядные сдвиговые суммы до 6 бит (поэтому сдвиг на 32 приводит к нулю). Из-за этих различий в оборудовании поведение полностью Undefined на C...

Nate, что обсуждение состояло в смещении суммы, большей, чем размер регистра. Но его ближайший я нашел для объяснения ограничений сдвига от авторитета.

Я думаю, что вторая причина - изменение потенциального знака на машине с двумя комплиментами. Но я никогда его нигде не читал (без обид @sellibitze (и я согласен с ним)).

Ответ 6

Поведение в С++ 03 такое же, как в С++ 11 и C99, вам просто нужно выйти за пределы правила для сдвига влево.

В разделе 5p5 Стандарта говорится, что:

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

Выражения с левым сдвигом, которые специально вызывают в C99 и С++ 11 как поведение undefined, являются теми же, которые оценивают результат за пределами диапазона представляемых значений.

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

Ответ 7

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

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

К сожалению, даже при том, что никогда не было никаких реализаций C99, которые не используют математику с двумя дополнениями, авторы C11 отказались определять поведение общего случая (не переполнение); IIRC утверждал, что это помешает "оптимизации". Наличие оператора левого сдвига вызывает неопределенное поведение, когда левый операнд является отрицательным, позволяет компиляторам предполагать, что смещение будет достижимо только тогда, когда левый операнд неотрицателен.

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

Ответ 8

Результат сдвига зависит от числового представления. Сдвиг ведет себя как умножение только тогда, когда числа представлены как два дополнения. Но проблема не только в отрицательных числах. Рассмотрим 4-битное число со знаком, представленное в избытке-8 (так называемый смещенный двоичный код). Число 1 представлено как 1 + 8 или   1001 Если мы оставим сдвиг как биты, мы получим   0010 который является представлением для -6. Аналогично, -1 представляется как -1 + 8   0111 который становится   1110 при сдвиге слева, представление для +6. Побитовое поведение хорошо определено, но числовое поведение сильно зависит от системы представления.