Является ли (int32_t) 255 << 24 неопределенным поведением в gcc (C ++ 11)? - PullRequest
0 голосов
/ 17 декабря 2018

В C ++ 11, согласно en.cppreference.com ,

Для подписанного и неотрицательного a значение << b равно <em>a * 2 b , если оно представимо в возвращаемом типе, в противном случае поведение не определено.

Насколько я понимаю, с 255 * 2 24 не представляется как int32_t, оценка (int32_t) 255 << 24 дает неопределенное поведение.Это верно?Может ли это зависеть от компилятора?Это среда IP16, если это имеет значение.

Справочная информация: это исходит из аргумента, который у меня есть с пользователем на arduino.stackexchange.com.По его словам, «в этом нет ничего неопределенного»:

вы заметили, что большая часть сдвига битов «определяется реализацией».Таким образом, вы не можете цитировать главу и стих из спецификации.Вы должны обратиться к документации GCC, так как это единственное место, которое может рассказать вам, что на самом деле происходит. gnu.org / software / gnu-c-manual / gnu-c-manual.html # Bit-Shifting - это только "неопределенное" для отрицательного значения сдвига.


Редактировать : Судя по ответам, мое чтение стандарта C ++ 11 кажется правильным.Тогда ключевая часть моего вопроса - вызывает ли это выражение неопределенное поведение в gcc .Как отмечает в своем комментарии davmac, я спрашиваю: «Определяет ли GCC, реализация, поведение, даже если оно не определено языковым стандартом».

Из руководства gcc, с которым я связан, может показаться,оно действительно определено, хотя я нахожу, что формулировка этого руководства больше похожа на учебник, чем на «закон о языке».Из ответа PSkocik (и комментария Кейна к этому ответу) вместо этого может показаться, что он не определен.Так что я все еще сомневаюсь.

Полагаю, моя мечта заключалась бы в том, чтобы в какой-то документации gcc было четкое утверждение о том, что 1) gcc не определяет поведения, явно не определенного в стандарте, или 2)gcc определяет это поведение в версии XX.XX и обязуется сохранить его во всех последующих версиях.

Редактировать 2 : PSkocik удалил свой ответ, который, на мой взгляд, вызывает сожаление, поскольку предоставил интересную информацию,Из его ответа, комментария Кейна к ответу и моих собственных экспериментов:

  1. (int32_t)255<<24 выдает ошибку времени выполнения при компиляции с помощью clang и -fsanitize=undefined
  2. тот же код не выдаетошибка с g ++ даже при -fsanitize=undefined
  3. (int32_t)256<<24 действительно дает ошибку времени выполнения при компиляции с g++ -std=c++11 -fsanitize=undefined

Точка 2 согласуется с интерпретацией, что gcc в C +Режим +11, определяет сдвиг влево более широко, чем стандартный.Согласно пункту 3, это определение может быть просто определением C ++ 14.Однако точка 3 не соответствует с идеей, что указанное в руководстве руководство является полным определением << в gcc (режим C ++ 11), так как это руководство не дает никаких намеков на то, что(int32_t)256<<24 может быть неопределенным.

Ответы [ 4 ]

0 голосов
/ 17 декабря 2018

Компилятор GNU C полностью определяет сдвиг влево / вправо, как описано в руководстве :

GCC поддерживает только два целых числа дополнениятипы и все битовые комбинации являются обычными значениями.

.,.

Как расширение языка C, GCC не использует широту, указанную в C99 и C11, только для обработки определенных аспектов со знаком '<<' как неопределенных.Однако -fsanitize=shift-fsanitize=undefined) будут диагностировать такие случаи.Они также диагностируются там, где требуются постоянные выражения.

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

Что касается компилятора GNU C ++ , документация действительно не имеет .Мы можем только догадываться, что по пропуску сдвиг работает в G ++ так же, как в GCC, хотя, по крайней мере, дезинфицирующее средство , похоже, знает о языковых различиях:

-fsanitize=shift

Этот параметр позволяет проверить, что результат операции сдвига не является неопределенным.Обратите внимание, что то, что считается неопределенным, немного отличается между C и C ++, а также между ISO C90 и C99 и т. Д.

0 голосов
/ 17 декабря 2018

Для компиляторов C ++ до C ++ 14, если у вас была такая функция:

// check if the input is still positive,
// after a suspicious shift

bool test(int input)
{
    if (input > 0) 
    {
        input = input << 24;

        // how can this become negative in C++11,
        // unless you are relying on UB?

        return (input > 0); 
    }
    else 
    {
        return false;
    }
}

, тогда оптимизирующему компилятору было бы разрешено изменить его на следующее:

bool test(int input)
{
    // unless you are relying on UB, 
    // input << 24 must fit into an int,
    // and input is positive in that branch

    return (input > 0);
}

И все довольны, потому что вы получаете хорошее ускорение в своих сборках релиза.

Я, однако, не знаю о компиляторах, которые действительно выполняют такую ​​оптимизацию для сдвигов влево, хотя это довольно часто встречается для дополнений кбыть оптимизирован, как видно в этом примере .

0 голосов
/ 17 декабря 2018

Это изменилось с течением времени и по уважительной причине, так что давайте пройдемся по истории.Обратите внимание, что во всех случаях простое выполнение static_cast<int>(255u << 24) всегда было определенным поведением.Может быть, просто сделайте это и обойдите все проблемы.

Исходная формулировка C ++ 11 :

Значение E1 << E2 равно E1 смещенным влево E2 битовым позициям;освобожденные биты заполнены нулями.Если E1 имеет тип без знака, значение результата будет E1×2<sup>E2</sup>, уменьшено по модулю на единицу больше максимального значения, представляемого в типе результата.В противном случае, если E1 имеет тип со знаком и неотрицательное значение, а E1×2E2 представимо в типе результата, то это результирующее значение;в противном случае поведение не определено.

255 << 24 является неопределенным поведением в C ++ 11, поскольку результирующее значение не представляется как 32-разрядное целое число со знаком, оно слишком большое.

Это неопределенное поведение вызывает некоторые проблемы, потому что constexpr должно диагностировать неопределенное поведение - и поэтому некоторые распространенные подходы к установке значений привели к серьезным ошибкам.Следовательно, CWG 1457 :

Текущая формулировка пункта 8.8 [expr.shift] делает неопределенным поведение для создания наиболее отрицательного целого числа данного типа путем сдвига влево(подписанный) 1 в знаковый бит, даже если это не редкость и работает корректно на большинстве архитектур (с двойным дополнением) [...]. В результате этот метод не может использоваться в константном выражении, котороесломает значительное количество кода.

Это был дефект, примененный к C ++ 11.Технически, соответствующий компилятор C ++ 11 реализовал бы все отчеты о дефектах, и поэтому было бы правильно сказать, что в C ++ 11 это , а не неопределенное поведение;поведение для 255 << 24 в C ++ 11 определено как -16777216.

Формулировку после дефекта можно увидеть в C ++ 14 :

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

Не было никаких изменений в формулировке / поведении в C ++ 17.

Но для C ++ 20 в результате Подписанные целые числа являются дополнением к двум (и его текстовая бумага ), формулировка значительно упрощена :

Значение E1 << E2является уникальным значением, равным E1×2<sup>E2</sup> по модулю 2<sup>N</sup>, где N является показателем диапазона типа результата.

255 << 24 все еще имеет определенное поведение в C ++ 20(с тем же результирующим значением), просто спецификация того, как мы туда попадаем, становится намного проще, потому что язык не должен обходиться вокруг факта, что представление для целых чисел со знаком было определено реализацией.

0 голосов
/ 17 декабря 2018

«Неопределенное поведение - это то, к чему стандарт не предъявляет никаких требований».это означает, что это может быть даже ожидаемое / правильное поведение.Этот стандарт C ++ указывает в отношении операторов сдвига.

8.5.7 Операторы сдвига [expr.shift] (черновик стандарта C ++ N4713)

  1. Операнды должны быть целочисленного или незаданного типа перечисления, и выполняются интегральные преобразования.Тип результата - тип повышенного левого операнда.Поведение не определено, если правый операнд отрицательный или больше или равен длине в битах повышенного левого операнда.В противном случае, если E1 имеет тип со знаком и неотрицательное значение и E1×2<sup>E2</sup> представимо в соответствующем типе без знака типа результата, то это значение, преобразованное в тип результата, является результирующим значением;в противном случае поведение не определено.

Как отмечает @rustyx ниже, "формулировка" E1×2<sup>E2</sup> может быть представлена ​​в соответствующем типе без знака типа результата " C ++ 14 .UB in C ++ 11 , к сожалению. "

...