Когда стандарты C были кодифицированы, разные платформы делали разные вещи, когда сдвигались влево отрицательные целые числа.На некоторых из них поведение может вызывать специфичные для реализации ловушки, поведение которых может находиться за пределами контроля программы и которое может включать выполнение произвольного кода.Тем не менее, возможно, что программы, написанные для таких платформ, могут использовать такое поведение (например, программа может указать, что пользователь должен будет сделать что-то для настройки обработчиков системных ловушек, прежде чем запускать ее, но затем программа может использовать поведениесоответствующим образом сконфигурированные обработчики прерываний).
Авторы стандарта C не хотели говорить, что компиляторы для машин, на которых смещение влево отрицательных чисел может прерываться, должны быть изменены, чтобы предотвратить такое прерывание (поскольку программы могут потенциальнополагаться на это), но если смещению влево отрицательного числа разрешено вызывать ловушку, которая может вызвать любое произвольное поведение (в том числе случайное выполнение кода), что означает, что смещению влево отрицательного числа разрешается делать что угодно.Следовательно, неопределенное поведение.
На практике примерно до 5 лет назад 99 +% компиляторов, написанных для машины, в которой использовалась математика с двумя дополнительными компонентами (то есть 99 +% машин, изготовленных с 1990 года), последовательно давали следующееповедения для x<<y
и x>>y
в той степени, в которой код полагался на такое поведение, считался не более непереносимым, чем код, который предполагал, что char
был 8 битами.Стандарт C не предписывал такое поведение, но любой автор компилятора, желающий быть совместимым с широкой базой существующего кода, следовал бы этому.
- , если
y
является типом со знаком, x << y
и x >> y
оцениваются, как если бы y
был приведен к неподписанному. - , если
x
имеет тип int
, x<<y
эквивалентно (int)((unsigned)x << y)
. - , если
x
имеет тип int
и положительный, x>>y
эквивалентен (unsigned)x >> y
.Если x
имеет тип int
и отрицательный, x>>y
эквивалентно `~ (~ ((без знака) x) >> y). - Если
x
имеет тип long
применяются аналогичные правила, но с unsigned long
вместо unsigned
. - , если
x
является N-битным типом и y
больше, чем N-1, тогда x >> y
и x << y
может произвольно дать ноль или может действовать так, как если бы правый операнд был y % N
;им может потребоваться дополнительное время, пропорциональное y
[обратите внимание, что на 32-разрядной машине, если y
отрицательно, это может потенциально быть long времени, хотя я знаю только одну машину, которая бына практике выполнить более 256 дополнительных шагов].Компиляторы не обязательно были последовательны в своем выборе, но всегда возвращали одно из указанных значений без других побочных эффектов.
К сожалению, по какой-то причине я не могу понять, авторы компиляторов решили, чтовместо того, чтобы позволять программистам указывать, какие предположения компиляторы должны использовать для удаления мертвого кода, компиляторы должны предполагать, что невозможно выполнить любое изменение, поведение которого не предписано стандартом C.Таким образом, для данного кода, подобного следующему:
uint32_t shiftleft(uint32_t v, uint8_t n)
{
if (n >= 32)
v=0;
return v<<n;
}
компилятор может определить, что, поскольку код будет участвовать в неопределенном поведении, когда n равно 32 или больше, компилятор может предположить, что if
никогда не вернет trueи, следовательно, может опустить код.Следовательно, до тех пор, пока кто-либо не придумает стандарт для C, который восстанавливает классическое поведение и позволяет программистам определять, какие предположения заслуживают удаления мертвого кода, такие конструкции не могут быть рекомендованы для любого кода, который может быть передан гиперсовременному компилятору.