В C89 поведение отрицательных значений, сдвигаемых влево, было однозначно определено на платформах с двумя дополнительными компонентами, которые не использовали биты заполнения для целочисленных типов со знаком и без знака. Биты значений, которые имеют подписанные и неподписанные типы, в общем случае должны находиться в одних и тех же местах, и единственное место, в котором может находиться бит знака для подписанного типа, находится там же, где верхний бит значения для неподписанных типов, который, в свою очередь, должен был быть слева от всего остального.
Обязательное поведение C89 было полезным и разумным для платформ с двумя дополнениями без дополнения, по крайней мере в тех случаях, когда обработка их как умножения не вызывала переполнения. Поведение может быть не оптимальным на других платформах или в реализациях, которые стремятся надежно перехватить целочисленное переполнение со знаком. Авторы C99, вероятно, хотели предоставить гибкость реализации в тех случаях, когда обязательное поведение C89 было бы далеко не идеальным, но ничто в обосновании не предполагает намерение, чтобы реализации качества не продолжали вести себя по-старому в тех случаях, когда было нет веских оснований поступать иначе.
К сожалению, даже при том, что никогда не было никаких реализаций C99, которые не используют математику с двумя дополнениями, авторы C11 отказались определять поведение общего случая (без переполнения); IIRC утверждал, что это помешает "оптимизации". Наличие оператора левого сдвига вызывает неопределенное поведение, когда левый операнд является отрицательным, позволяет компиляторам предполагать, что смещение будет достижимо только тогда, когда левый операнд неотрицателен. Это позволяет компиляторам получать код вроде:
int do_something(int x)
{
if (x >= 0)
{
launch_missiles();
exit(1);
}
return x<<4;
}
, чтобы признать, что такой метод никогда не будет вызываться с отрицательным значением для x
, и, таким образом, тест if
можно удалить, а вызов launch_missiles()
сделать безусловным. Поскольку известно, что exit
не возвращает, компилятор также может опустить вычисление x<<4
. Если бы не такое правило, программисту пришлось бы вставлять какую-то неуклюжую директиву __assume(x >= 0);
, чтобы запросить такое поведение, но выполнение сдвига влево отрицательных значений Undefined Behavior устраняет необходимость иметь программиста, который явно хочет такую семантику ( посредством выполнения левого сдвига), чтобы загромождать код с ними.
Заметьте, между прочим, в гипотетическом событии, которое код вызывал do_something(-1)
, это будет связано с неопределенным поведением, поэтому вызов launch_missiles будет совершенно законным действием.