Я пытаюсь создать предупреждение о неопределенном поведении при смещении влево отрицательного числа. Согласно ответу это сдвиг влево отрицательного числа в C не определен.
The result of E1 << E2 is E1 left-shifted E2 bit positions; vacated bits are filled with zeros. If E1 has an unsigned type, the value of the result is E1×2E2, reduced modulo one more than the maximum value representable in the result type. If E1 has a signed type and non-negative value, and E1×2E2 is representable in the result type, then that is the resulting value; otherwise, the behaviour is undefined.
Я пытаюсь понять, почему я не получаю предупреждение с этим кодом:
x << 3
gcc-Стена (версия 9.1.0)
int main ()
{
int x= -4, y, z=5;
y = z << x;
y = x << 3;
return y;
}
Отдельно стоит отметить, что меня также не предупреждают о смещении влево на отрицательное число.
z << x
Но вы делать получаете предупреждение за y = z << -4; (ваше последнее предложение). Компилятор пытается помочь, но не запускает код, чтобы определить, всегда ли x отрицательно. Это может быть даже не знать, если значение для x является вводом пользователя во время выполнения.





Поведение при сдвиге влево любого значения на величину, меньшую размера слова, было определено в C89 для всех целочисленных значений левого операнда, даже несмотря на то, что указанное поведение для отрицательных левых операндов часто было бы далеко не идеальным на недвойках. дополняющие дополняющие платформы или на платформах с отловом ошибок в случаях, когда многократное умножение на два приведет к переполнению.
Чтобы не требовать, чтобы реализации вели себя далеко не идеально, авторы C99 решили разрешить реализациям обрабатывать такие случаи так, как они считают нужным. Вместо того, чтобы пытаться угадать все места, где у реализаций могут быть веские причины отклоняться от поведения C89, авторы стандарта C99, по-видимому, ожидали, что реализации будут следовать поведению C89, отсутствие веской причины для иного, независимо от того, обязывает ли их к этому Стандарт или нет. нет.
Если авторы Стандарта намеревались внести какие-либо изменения в обработку операций сдвига влево в случаях, когда поведение C89 имело смысл, в опубликованном документе «Обоснование» должно быть какое-то упоминание об этом изменении. Ничего подобного нет. Единственный вывод, который я могу сделать из такого упущения, состоит в том, что переход от «Вести себя определенным образом, который почти всегда имеет смысл» к «Вести себя таким образом, если разработчик считает, что это имеет смысл, и в противном случае обрабатывать код любым другим способом». способ, который разработчик считает нужным», не рассматривался как достаточное изменение, чтобы оправдать комментарий. Тот факт, что авторам Стандарта не удалось предписать поведение, никоим образом не означает, что они не ожидали, что реализации общего назначения с дополнением до двух не будут продолжать обрабатывать такие сдвиги, как они всегда это делали, и что программисты не должны иметь право на аналогичные ожидания.
Вопрос не спрашивает, почему сдвиг влево отрицательного значения не определен или какова история. Он спрашивает, почему компилятор не выдает предупреждающее сообщение для z << x или x << 3, когда x отрицательное (несмотря на то, что оно выдает для z << -4 или -4 << 3).
@EricPostpischil: многие компиляторы определяют больше вариантов поведения, чем тот минимум, который описан в стандарте. Поведение было определено в C89, и авторы gcc, вероятно, думали, что проще обрабатывать действие как определенное, независимо от того, находится ли компилятор в режиме C89 или C99, чем обрабатывать его только как определенное в первом случае.
В 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, компилятор может сделать вывод, что в данном конкретном случае сдвиги не определены, и выдать предупреждение. Этого не требует стандарт, и неспособность компилятора сделать это просто зависит от качества реализации компилятора.
Кажется, я упомянул, что использовал «gcc -Wall». Я нет упомянул версию. Это 9.1.0. Что касается x <<3, я думаю, что что-то вроде распространения копирования поможет компилятору понять, что x было отрицательным значением. Я только что заметил, что для gcc компилятор даже не предупреждает меня о -4 << 3, хотя он предупреждает меня о 5 << -4. godbolt.org/z/i34yM2
Кроме того, учитывая, что компилятор не предупреждает, что x << 3 не определено (что в данном случае определенно так), могу ли я предположить, что он не будет выполнять скрытую оптимизацию за моей спиной (предполагая неопределенное поведение)?.
@abjoshi: Нет, отсутствие предупреждения компилятора не означает, что вы можете предположить, что компилятор не будет «оптимизировать» код.
Надеюсь, я ясно дал понять, что не имею в виду общую оптимизацию. Я просто имею в виду те, которые выполняются конкретно, потому что компилятор предполагает, что у нас не будет неопределённого поведения в нашей программе.
@abjoshi: отсутствие предупреждения компилятора не означает, что компилятор не заменит полностью неопределенное поведение в вашем коде чем угодно.
Если две реализации поддерживают популярное расширение «обрабатывать сдвиги влево так же, как C89, даже в тех случаях, когда C99 больше не требует этого», я бы вообще не считал, что одна из них затрудняет надежное использование этого расширения без сигналов компилятора. более высокого качества, чем тот, который позволяет программистам просто использовать это расширение, не загромождая диагностический отчет.
Краткое объяснение того, что компилятор не выдает предупреждения в таких случаях, заключается в том, что это не требуется.
Определение «не определено» в стандарте также прямо указывает «не требуется диагностика». Это означает, что стандарт не требует реализации для выдачи диагностики в таких случаях. Причина этого в том, что технически компиляторы могут быть не в состоянии обнаружить все случаи неопределенного поведения в течение разумного времени или (в некоторых случаях) вообще. Некоторые случаи могут быть обнаружены только во время выполнения. Компиляторы представляют собой сложные фрагменты кода, поэтому, даже если человек может легко распознать проблему, компилятор может этого не сделать (с другой стороны, компиляторы также обнаруживают проблемы, которые люди не могут легко найти).
Когда диагностика не требуется, есть несколько вещей — все по усмотрению — которые должны произойти, прежде чем компилятор выдаст предупреждение.
Первое, что происходит, сводится к проблемам «качества реализации» — поставщик или разработчик компилятора решает написать код, который обнаруживает определенные случаи, и другой код, который выдает предупреждение.
Два шага (обнаружение случая и предупреждение о нем) являются отдельными и полностью дискреционными — стандарт не требует диагностики, поэтому, даже если написан код, обнаруживающий конкретный случай, компилятор все равно не обязан выдавать предупреждение об этом. Это. На практике, даже если проблемы обнаружены, разработчики компилятора могут не выдавать предупреждение по разным причинам. Некоторые предупреждения относятся к редким пограничным случаям, поэтому не стоит их выдавать. Некоторым предупреждениям присуща частота «ложных срабатываний», что приводит к тому, что разработчики жалуются в отчетах об ошибках поставщику компилятора о ненужных предупреждениях, поэтому компилятор по умолчанию настроен так, чтобы не выдавать их.
Следующее требование для выдачи предупреждения состоит в том, что пользователь компилятора должен выбрать отображение предупреждений. Благодаря яростному лоббированию поставщиков разработчиками - большинство современных компиляторов настроены ПО УМОЛЧАНИЮ, поэтому они выдают относительно небольшое количество предупреждений. Поэтому разработчикам, которым требуется как можно больше помощи от компилятора, необходимо явно включить его. Например, используя опции -Wall с gcc и clang.
x << 3? Поскольку левый операндxможет быть знаковым или беззнаковым. Стандарт не ограничивает, чтоxдолжен быть только беззнаковым типом, т. е. он может быть подписанным, т. е. может содержать отрицательные значения.