Простой фрагмент кода:
#define FOO 7
int bar = -875;
bar <<= FOO;
Об этом сообщает UBSAN as UB.
Насколько я понимаю, -875 << 7
- это просто -(875<<7)
и переполнения нет.
Итак, есть ли здесь реальная проблема?
Простой фрагмент кода:
#define FOO 7
int bar = -875;
bar <<= FOO;
Об этом сообщает UBSAN as UB.
Насколько я понимаю, -875 << 7
- это просто -(875<<7)
и переполнения нет.
Итак, есть ли здесь реальная проблема?
Ваше понимание неверно.
Сначала вы использовали синтаксис bar <<= FOO
. Это явно сдвигает bar
, а bar
отрицательно. Сдвиг отрицательных значений влево приводит к неопределенному поведению в C. Невозможно интерпретировать bar <<= FOO
как -(875<<7)
.
Во-вторых, по поводу -875 << 7
с точки зрения приоритета операторов: унарные операторы всегда имеют более высокий приоритет, чем бинарные, а значит, -875 << 7
это (-875) << 7
, а не -(875 << 7)
. И снова сдвиг влево отрицательных значений приводит к неопределенному поведению в C.
На машине знаковых величин неясно, каков должен быть эффект смещения отрицательного числа влево, и для такой машины не было бы неразумным ловушку, если бы была предпринята такая операция. На такой машине наложение каких-либо требований на поведение отрицательного целочисленного сдвига влево, вероятно, потребовало бы от компиляторов для таких машин генерации дополнительного кода даже в тех случаях, когда значения, которые нужно сдвинуть, всегда были бы положительными. Чтобы избежать таких затрат, авторы Стандарта отказались от обязательного выполнения какого-либо конкретного поведения.
У платформ с дополнением до единицы и с дополнением до единицы не было бы логической причины ловушки при смещении отрицательного значения (хотя было бы неясно, должно ли -1‹‹1 давать -2 или -3 на машине с дополнением до единицы), но авторы стандарта не видели причин говорить, что сдвиг отрицательных значений влево имеет неопределенное поведение на платформах, использующих целые числа со знаком, поведение, определяемое реализацией, на платформах, использующих дополнение до единицы, и поведение, определенное стандартом на платформах, которые используют дополнение до двух, поскольку любая реализация с дополнением до двух расценит -1‹‹1 как получение -2, вне зависимости от того, предписано ли это Стандартом, если только автор не намеренно запутал.
Вероятно, до 2005 года или около того не было ничего даже вообразимо опасного в коде, который будет запускаться только на обычных машинах с дополнением до двух, использующих оператор сдвига влево для отрицательного значения. К сожалению, примерно в то же время начала распространяться идея, предполагающая, что компилятор, который избегает делать что-либо, не предписываемое Стандартом, может быть более «эффективным», чем компилятор, который ведет себя полезно в ситуациях, не предусмотренных Стандартом, и что такая «эффективность "желательно. Я еще не знаю, что компиляторы рассматривают оператор y=x<<1;
как задним числом делающий значение x неотрицательным, но я не думаю, что есть основания полагать, что они не будут делать этого в будущем, поэтому до тех пор, пока какое-либо агентство официально не кодифицирует поведенческие гарантии, которые основные компиляторы C для микрокомпьютеров единогласно поддерживали в течение 25 с лишним лет, такой код не может считаться «безопасным».