C сдвиг вправо неправильно работает с типами int в моей программе

У меня есть следующая функция в C:

int lrot32(int a, int n)
{
    printf("%X SHR %d = %X\n",a, 32-n, (a >> (32-n)));
    return ((a << n) | (a >> (32-n)));
}

Когда я передаю в качестве аргументов lrot32(0x8F5AEB9C, 0xB), я получаю следующее:

8F5AEB9C shr 21 = FFFFFC7A

Однако результат должен быть 47А. Что я делаю не так?

Спасибо за ваше время


person Marc    schedule 01.07.2017    source источник
comment
Обратите внимание, что название вашего вопроса подразумевает, что многие программы не работают.   -  person Iharob Al Asimi    schedule 01.07.2017
comment
Вы пробовали вставить 0x8F5AEB9C в int на своей платформе?   -  person WhozCraig    schedule 01.07.2017
comment
При работе с битами и побитовыми операциями всегда используйте беззнаковые типы.   -  person Some programmer dude    schedule 01.07.2017


Ответы (1)


int — целочисленный тип со знаком. C11 6.5.7p4-5 говорит следующее:

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

5 Результатом E1 >> E2 является E1 сдвинутых вправо E2 битовых позиций. [...] если E1 имеет тип со знаком и неотрицательное значение, значение результата является неотъемлемой частью частного E1 / 2E2 . Если E1 имеет тип со знаком и отрицательное значение, результирующее значение определяется реализацией.

Таким образом, в случае <<, если сдвинутое значение отрицательно или положительное значение после сдвига не представляется в типе результата (здесь: int), поведение не определено; в случае >>, если значение отрицательное, результатом является определенная реализация.

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


Однако, если вы хотите настроить таргетинг на конкретный компилятор, проверьте в его руководствах, каким будет поведение, если оно указано. Например, GCC говорит :

Результаты некоторых побитовых операций над целыми числами со знаком (C90 6.3, C99 и C11 6.5).

Побитовые операторы работают с представлением значения, включая биты знака и значения, где бит знака считается непосредственно над битом самого высокого значения. Знак ">>" действует на отрицательные числа по расширению знака. [*]

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

[*] расширение знака здесь означает, что бит знака, равный 1 для отрицательных целых чисел, повторяется на величину сдвига при выполнении сдвига вправо - вот почему вы видите эти F в результате.

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


Возможно, вы захотите использовать беззнаковые целые числа - unsigned int или, скорее, если ожидается определенная ширина, то, например, uint32_t, так как сдвиги всегда четко определены для нее и, казалось бы, соответствуют вашим ожиданиям.


Еще одна вещь, которую следует отметить, это то, что не все суммы смен разрешены. C11 6.5.7 p3:

[...] Если значение правого операнда отрицательно или больше или равно ширине расширенного левого операнда, поведение не определено.

Таким образом, если вы когда-нибудь сдвинете целое число без знака шириной 32 бита на 32 — влево или вправо, поведение будет неопределенным. Это следует иметь в виду. Даже если бы компилятор не сделал ничего странного, некоторые процессорные архитектуры действуют так, как если бы смещение на 32 сдвинуло бы все биты, а другие ведут себя так, как если бы величина сдвига была равна 0.

person Antti Haapala    schedule 01.07.2017
comment
Возможно, стоит отметить, что в большинстве современных реализаций знаковый бит смещается на >> - это то, что видит OP. - person tofro; 01.07.2017
comment
uint32_t был бы лучшим типом, так как цель, по-видимому, заключается в 32-битных операциях. - person M.M; 01.07.2017