UBSAN сообщает: -875 ‹‹ 7 как неопределенное поведение

Простой фрагмент кода:

#define FOO 7
int bar = -875;
bar <<= FOO;

Об этом сообщает UBSAN as UB.

Насколько я понимаю, -875 << 7 - это просто -(875<<7) и переполнения нет.

Итак, есть ли здесь реальная проблема?


person Jacko    schedule 20.04.2016    source источник


Ответы (2)


Ваше понимание неверно.

Сначала вы использовали синтаксис bar <<= FOO. Это явно сдвигает bar, а bar отрицательно. Сдвиг отрицательных значений влево приводит к неопределенному поведению в C. Невозможно интерпретировать bar <<= FOO как -(875<<7).

Во-вторых, по поводу -875 << 7 с точки зрения приоритета операторов: унарные операторы всегда имеют более высокий приоритет, чем бинарные, а значит, -875 << 7 это (-875) << 7, а не -(875 << 7). И снова сдвиг влево отрицательных значений приводит к неопределенному поведению в C.

person AnT    schedule 20.04.2016
comment
Спасибо, @АнТ. В моем случае я конвертирую в 7-битную фиксированную точку, когда выполняю сдвиг. Итак, каков правильный способ преобразования отрицательных чисел в фиксированную точку? - person Jacko; 20.04.2016
comment
@Jacko: Умножить вместо сдвига? Умножьте его на 128. Компилятор все равно сможет сгенерировать сдвиг для этого (если это действительно самый эффективный способ сделать это), но вам не придется проходить через UB. - person AnT; 20.04.2016
comment
Из стандарта C11: 6.5.7 Операторы побитового сдвига [...] Результатом E1 ‹‹ E2 является E1 сдвинутая влево битовая позиция E2; освободившиеся биты заполняются нулями. Если E1 имеет беззнаковый тип, значение результата равно E1 × 2^E2, уменьшенному по модулю на единицу больше, чем максимальное значение, представленное в типе результата. Если E1 имеет тип со знаком и неотрицательное значение, а E1 × 2^E2 может быть представлено в типе результата, то это и есть результирующее значение; в противном случае поведение не определено. - person njuffa; 20.04.2016

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

У платформ с дополнением до единицы и с дополнением до единицы не было бы логической причины ловушки при смещении отрицательного значения (хотя было бы неясно, должно ли -1‹‹1 давать -2 или -3 на машине с дополнением до единицы), но авторы стандарта не видели причин говорить, что сдвиг отрицательных значений влево имеет неопределенное поведение на платформах, использующих целые числа со знаком, поведение, определяемое реализацией, на платформах, использующих дополнение до единицы, и поведение, определенное стандартом на платформах, которые используют дополнение до двух, поскольку любая реализация с дополнением до двух расценит -1‹‹1 как получение -2, вне зависимости от того, предписано ли это Стандартом, если только автор не намеренно запутал.

Вероятно, до 2005 года или около того не было ничего даже вообразимо опасного в коде, который будет запускаться только на обычных машинах с дополнением до двух, использующих оператор сдвига влево для отрицательного значения. К сожалению, примерно в то же время начала распространяться идея, предполагающая, что компилятор, который избегает делать что-либо, не предписываемое Стандартом, может быть более «эффективным», чем компилятор, который ведет себя полезно в ситуациях, не предусмотренных Стандартом, и что такая «эффективность "желательно. Я еще не знаю, что компиляторы рассматривают оператор y=x<<1; как задним числом делающий значение x неотрицательным, но я не думаю, что есть основания полагать, что они не будут делать этого в будущем, поэтому до тех пор, пока какое-либо агентство официально не кодифицирует поведенческие гарантии, которые основные компиляторы C для микрокомпьютеров единогласно поддерживали в течение 25 с лишним лет, такой код не может считаться «безопасным».

person supercat    schedule 20.04.2016
comment
Спасибо, @supercat. Я учусь относиться к сменным операторам с большим уважением :) Быстро и потенциально смертельно. - person Jacko; 20.04.2016
comment
@Jacko: Надеюсь, когда-нибудь произойдет признанный разрыв между разумным C, где регистры сдвига будут свободны от побочных эффектов, и модным в настоящее время Obtuse C, который изо всех сил старается не позволять программистам делать что-либо полезное, кроме того, что Стандарт требует. - person supercat; 20.04.2016
comment
Что касается последнего абзаца, может быть полезно прочитать следующее: Wang, Xi, et al. Дифференциальный подход к обнаружению неопределенного поведения. Сообщения ACM 59.3 (2016): 99-106. (онлайн) - person njuffa; 20.04.2016
comment
Что произошло в 2005 году, чтобы начать неопределенную :=› невыполненную тенденцию? Я только недавно осознал это, и это причиняет боль моему мозгу. Я чувствую, что неписаный контракт между программистом и компилятором нарушается. - person AShelly; 20.04.2016
comment
@AShelly: я не уверен, что ускорило это, но я не нашел никаких доказательств тенденции до этого. С тех пор, как я начал искать, я не находил никаких доказательств так далеко, но у меня достаточно воспоминаний о том, что в 2005 году что-то произошло, и я не могу утверждать, что не знал ничего до этого. - person supercat; 20.04.2016
comment
@AShelly: Что касается контракта, все просто: стандарт C89 был написан с намерением сформировать общее ядро ​​для различных машинно-специфических диалектов, которые будут расширены, чтобы соответствовать уникальным возможностям разных платформ. Авторы обоснования отметили, например, что преобразование unsigned char в signed int не помешает большинству современных платформ давать детерминированное арифметически правильное поведение, если unsigned x=uchar1*int1; дал результат между INT_MAX+1u и UINT_MAX, даже если такой код может дать сбой на других платформах. - person supercat; 20.04.2016
comment
@AShelly: Многие считали важным, чтобы компиляторы не только соответствовали Стандарту, но и соблюдали поведенческие гарантии, предлагаемые компиляторами для аналогичных платформ. Если бы 100,0% компиляторов соблюдали поведенческую гарантию, за исключением случаев, когда они вызываются в гиперпедантичном режиме, ни у кого не было бы причин думать, что это имеет значение, действительно ли это требуется Стандартом. - person supercat; 20.04.2016
comment
@njuffa: Просматривая библиографию этой статьи, кажется, что рассматриваемая тенденция в основном возникла относительно недавно. Справочник 2007 года, который я видел (n+100 > n), относится к классу оптимизации, который я считаю полезным, который позволяет компиляторам обрабатывать целые числа с дополнительной точностью, которые могут появляться или исчезать на досуге компилятора - что-то очень отличное от гипермодернизм. - person supercat; 21.04.2016
comment
@supercat Я узнал об этой тенденции примерно в 2006 или 2007 году, когда меня укусило введение gcc новых оптимизаций, использующих неопределенное поведение, которые сломали код, который я без проблем использовал более десяти лет до этого, на трех платформах и пяти наборах инструментов. . Кстати, более подробная версия статьи тех же авторов в CACM: Wang, Xi, et al. Дифференциальный подход к обнаружению неопределенного поведения. Транзакции ACM в компьютерных системах (TOCS) 33.1 (2015): 1. - person njuffa; 21.04.2016
comment
@njuffa: Что меня раздражает, так это отношение сопровождающих gcc к тому, что код, основанный на поведении, которое не определено стандартом, но на 100% последовательно обрабатывается каждым современным компилятором, следует считать сломанным, особенно если исправление кода будет эффективным. блокировать то, что в противном случае было бы полезно для оптимизации. Если C должен выжить как полезный язык, ему необходимо добавить нормативные спецификации, чтобы программы могли указывать, какое поведение в крайних случаях им требуется, а компиляторы могли затем либо принимать программы (и соблюдать требования), либо отклонять программы. - person supercat; 21.04.2016
comment
@njuffa: Если авторы компилятора для какой-то платформы решат, что никто, пишущий код для этой платформы, не будет заботиться о какой-то гарантии, им не нужно будет обременять ею компилятор, но, с другой стороны, код, который выиграет от гарантия того, что почти любая платформа должна быть в состоянии предлагать дешево, не должна воздерживаться от ее использования только потому, что какая-то малоизвестная платформа не может этого сделать. - person supercat; 21.04.2016