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

Я пытаюсь сгенерировать предупреждение о неопределенном поведении при сдвиге влево отрицательного числа. Согласно этому ответу, сдвиг влево отрицательного числа в 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

3 ответа

Решение

В 5 << -4и GCC 9.1.0, и Apple LLVM 10.0.1 с clang-1001.0.46.4, нацеленные на x86-64, выдают предупреждающее сообщение ("счет смещения влево отрицательный" для GCC и "счет смещения отрицательный" для LLVM-clang), В -4 << 3GCC не выдает предупреждение, а 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Компилятор может сделать вывод, что сдвиги не определены в данном конкретном случае, и выдать предупреждение. Это не требуется стандартом, и неспособность компилятора сделать это - просто вопрос качества реализации компилятора.

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

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

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

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

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

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

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

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

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

Другие вопросы по тегам