Получайте предупреждение за сдвиг отрицательного числа влево

Я пытаюсь создать предупреждение о неопределенном поведении при сдвиге влево отрицательного числа. Согласно этому ответу сдвиг влево отрицательного число в C не определено.

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

Я пытаюсь понять, почему я не получаю предупреждение с помощью этого кода: x << 3

gcc -Wall (версия 9.1.0)

int main ()
{
    int x= -4, y, z=5;
    y = z << x;
    y = x << 3;
    return y;
}

Отдельно отметим, что меня также не предупреждают о смещении влево на отрицательное число z << x


person abjoshi - Reinstate Monica    schedule 29.06.2019    source источник
comment
почему я не получаю предупреждения с этим кодом: x << 3? Потому что левый операнд x может быть подписанным или беззнаковым. Стандарт не ограничивает, что x должен быть только беззнаковым типом, то есть это может быть подписанный тип, то есть он может содержать отрицательные значения.   -  person Achal    schedule 29.06.2019
comment
Но вы действительно получаете предупреждение за y = z << -4; (ваше последнее предложение). Компилятор пытается помочь, но не запускает код, чтобы определить, всегда ли x отрицательно. Он может даже не знать, если значение для x вводится пользователем во время выполнения.   -  person Weather Vane    schedule 29.06.2019


Ответы (3)


В 5 << -4 и GCC 9.1.0, и Apple LLVM 10.0.1 с clang-1001.0.46.4, нацеленные на x86-64, выдают предупреждающее сообщение («количество сдвигов влево отрицательное» для GCC и «количество сдвигов отрицательное» для LLVM- лязг). В -4 << 3 GCC не выдает предупреждения, но LLVM-clang делает («смещение отрицательного значения не определено»).

Стандарт C не требует диагностики ни в одном из этих случаев, поэтому независимо от того, делает ли компилятор проблему качества реализации (или, возможно, компилятор расширяет C, определяя сдвиги влево отрицательных значений и, следовательно, не учитывает это ошибка).

Когда операнд не является константой, как в z << x и x << 3, мы можем предположить, что компилятор не видит отрицательный операнд, поэтому он не выдает предупреждения. В общем, эти выражения могут иметь определенное поведение: если x не является отрицательным и находится в подходящих границах (не больше ширины z в первом случае и не настолько велик, чтобы x << 3 переполнялся во втором случае), поведение будет следующим. определены (за исключением возможности переполнения z << x). Стандарт C не говорит, что сдвиги влево с типами, которые могут иметь отрицательные значения, т.е., подписанные типы, не определены, просто сдвиги влево с отрицательными значениями не определены. Следовательно, компилятор будет ошибкой выдавать предупреждающее сообщение всякий раз, когда x был подписанным типом в выражении, таком как z << x или x << 3.

Конечно, учитывая int x = -4 и отсутствие каких-либо изменений в x между этой инициализацией и выражениями z << x и x << 3, компилятор может сделать вывод, что сдвиги не определены в этом конкретном случае, и выдать предупреждение. Стандарт не требует этого, и отказ компилятора сделать это просто вопрос качества реализации компилятора.

person Eric Postpischil    schedule 29.06.2019
comment
Думаю, я уже упоминал, что использовал gcc -Wall. Я не упомянул версию. Это 9.1.0. Что касается x ‹< 3, я могу представить, что что-то вроде распространения копий поможет компилятору определить, что x - отрицательное значение. Я только что заметил, что для gcc компилятор даже не предупреждает меня о -4 ‹---------------- 3, хотя он предупреждает меня о 5 ‹AC--4. godbolt.org/z/i34yM2 - person abjoshi - Reinstate Monica; 30.06.2019
comment
Кроме того, учитывая, что компилятор не предупреждает о том, что x ‹< 3 undefined (что определенно и есть в данном случае), могу ли я предположить, что он не будет выполнять скрытую оптимизацию за моей спиной (при условии неопределенного поведения) ?. - person abjoshi - Reinstate Monica; 30.06.2019
comment
@abjoshi: Нет, отсутствие предупреждения компилятора не означает, что вы можете предположить, что компилятор не будет «оптимизировать» код. - person Eric Postpischil; 30.06.2019
comment
Надеюсь, мне было ясно, что я не имею в виду общие оптимизации. Я имею в виду только те, которые выполняются специально, потому что компилятор предполагает, что у нас не будет неопределенного поведения в нашей программе. - person abjoshi - Reinstate Monica; 30.06.2019
comment
@abjoshi: отсутствие предупреждения компилятора не означает, что компилятор не заменит полностью неопределенное поведение в вашем коде чем угодно. - person Eric Postpischil; 30.06.2019
comment
Если две реализации поддерживают популярный процесс с левым сдвигом так же, как это делал C89, даже в тех случаях, когда C99 больше не требует его расширения, я бы в целом не считал, что один из них затрудняет надежное использование этого расширения без криков компилятора как более высокий качества, чем тот, который позволяет программистам просто использовать это расширение, не загромождая диагностический отчет. - person supercat; 01.07.2019

Краткое объяснение того, что компилятор не выдает предупреждение в таких случаях, заключается в том, что это не требуется.

Определение «undefined» в стандарте также явно указывает, что «диагностика не требуется». Это означает, что стандарт не требует реализации для выдачи диагностики в таких случаях. Причина этого в том, что технически компиляторы могут быть не в состоянии обнаружить все экземпляры неопределенного поведения в разумные сроки или (в некоторых случаях) вообще. Некоторые случаи могут быть обнаружены только во время выполнения. Компиляторы - это сложные фрагменты кода, поэтому - даже если человек может легко распознать проблему, - компилятор может этого не сделать (с другой стороны, компиляторы также обнаруживают проблемы, которые люди не могут легко найти).

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

Первое, что случается, вызывает беспокойство по поводу «качества реализации» - поставщик или разработчик компилятора решает написать код, который обнаруживает конкретные случаи, и другой код, который выдает предупреждение.

Два шага (обнаружение случая и предупреждение о нем) являются отдельными и полностью дискреционными - стандарт не требует диагностики, поэтому, даже если написан код, обнаруживающий конкретный случай, компилятор все равно не должен выдавать предупреждение о Это. На практике, даже если проблемы обнаружены, разработчики компилятора могут решить не выдавать предупреждение по разным причинам. Некоторые предупреждения относятся к редким крайним случаям, поэтому не стоит их выдавать. Некоторым предупреждениям присуща частота «ложных срабатываний», что приводит к тому, что разработчики жалуются на сообщения об ошибках поставщику компилятора о ненужных предупреждениях, поэтому компилятор по умолчанию настроен так, чтобы их не выдавать.

Следующее требование для выдачи предупреждения - это то, что пользователь компилятора должен выбрать отображение предупреждений. Благодаря яростному лоббированию производителей со стороны разработчиков - большинство современных компиляторов настроены ПО УМОЛЧАНИЮ, поэтому они выдают относительно небольшое количество предупреждений. Поэтому разработчикам, которым требуется максимально возможная помощь от компилятора, необходимо явно включить его. Например, используя -Wall параметры с gcc и clang.

person Peter    schedule 30.06.2019

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

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

Если авторы Стандарта намеревались внести какие-либо изменения в обработку операций сдвига влево в тех случаях, когда поведение C89 имело смысл, следует упомянуть об изменении в опубликованном документе Rationale. Нет вообще ничего. Единственный вывод, который я могу сделать из такого упущения, заключается в том, что переход от «Веди себя определенным образом, который почти всегда имеет смысл» к «Веди себя таким образом, если разработчик считает, что это имеет смысл, и в противном случае обрабатывать код любым другим способом». мода, которую разработчик считает подходящей, "не рассматривалась как изменение, достаточное для оправдания комментария. Тот факт, что авторы Стандарта не смогли предписать поведение, никоим образом не означает, что они не ожидали, что универсальные реализации с дополнением до двух не будут продолжать обрабатывать такие сдвиги, как они всегда делали, или что программисты не должны иметь право на аналогичные ожидания.

person supercat    schedule 29.06.2019
comment
Вопрос не в том, почему сдвиг влево на отрицательное значение не определен или какова история. Он спрашивает, почему компилятор не выдает предупреждающее сообщение для z << x или x << 3, когда x отрицательно (несмотря на то, что он делает для z << -4 или -4 << 3). - person Eric Postpischil; 30.06.2019
comment
@EricPostpischil: Многие компиляторы определяют больше поведений, чем минимум, описанный в Стандарте. Поведение было определено в C89, и авторы gcc, вероятно, думали, что было легче обрабатывать действие, как определено, независимо от того, находится ли компилятор в режиме C89 или C99, чем обрабатывать его только так, как определено в первом случае. - person supercat; 30.06.2019