Сдвиги, типы и знаки расширения в Си
У меня есть следующий код:
unsigned char chr = 234; // 1110 1010
unsigned long result = 0;
result = chr << 24;
И теперь результат будет равен 18446744073340452864, что 1111 1111 1111 1111 1111 1111 1111 1111 1110 1010 0000 0000 0000 0000 0000 0000
в двоичном
Почему происходит расширение знака, когда chr не подписан?
Также, если я изменю сдвиг с 24 на 8, результат будет 59904, который 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1110 1010 0000 0000
в двоичном Почему здесь не сделано расширение? (Любой сдвиг 23 или меньше не имеет расширения знака)
Также на моей нынешней платформе sizeof(long)
это 8.
Каковы правила автоматического приведения к типу большего размера при переключении? Мне кажется, что если сдвиг равен 23 или меньше, то chr преобразуется в неподписанный тип, а если в 24 или более он преобразуется в подписанный тип? (И почему расширение знака вообще делается с левым сдвигом)
2 ответа
Чтобы понять это, проще всего думать с точки зрения ценностей.
Каждый целочисленный тип имеет фиксированный диапазон представимых значений. Например, unsigned char
обычно колеблется от 0
в 255
; возможны другие диапазоны, и вы можете найти выбор вашего компилятора, проверив UCHAR_MAX
в limits.h
,
При выполнении преобразования между интегральными типами; если значение представимо в типе назначения, то результатом преобразования будет это значение. (Это может быть другой битовый шаблон, например расширение знака).
Если значение не представляется в типе назначения, то:
- для подписанных адресатов поведение определяется реализацией (что может включать в себя повышение сигнала).
- для беззнаковых адресатов значение корректируется по модулю максимального значения, представляемого в типе, плюс один.
Современные системы обрабатывают подписанное присвоение вне диапазона посредством усечения влево избыточных битов; и если он все еще находится за пределами диапазона, он сохраняет тот же битовый шаблон, но значение изменяется на любое значение, которое этот битовый шаблон представляет в типе назначения.
Переходя к вашему фактическому примеру.
В C есть то, что называется интегральной рекламой. С <<
это происходит с левым операндом; с арифметическими операторами это происходит со всеми операндами. Эффект интегрального продвижения состоит в том, что любое значение типа меньше int
преобразуется в то же значение с типом int
,
Далее определение << 24
это умножение на 2^24 (где это имеет тип повышенного левого операнда), с неопределенным поведением, если это переполняется. (Неофициально: сдвиг в знаковый бит вызывает UB).
Итак, если выразить все преобразования явно, ваш код
result = (unsigned long) ( ((int)chr) * 16777216 )
Теперь результат этого расчета 3925868544
что, если вы находитесь на типичной системе с 32-разрядным int
, больше, чем INT_MAX
2147483647, поэтому поведение не определено.
Если мы хотим изучить результаты этого неопределенного поведения в типичных системах: то, что может произойти, - это та же самая процедура, которую я описал ранее для назначения вне диапазона. Битовый паттерн 3925868544
конечно 1110 1010 0000 0000 0000 0000 0000 0000
, Рассматривая это как образец int
использование дополнения 2 дает инт -369098752
,
Наконец, у нас есть преобразование этого значения в unsigned long
, -369098752
вне диапазона для unsigned long
; и правило для неподписанного места назначения - корректировать значение по модулю ULONG_MAX+1
, Таким образом, ценность, которую вы видите 18446744073709551615 + 1 - 369098752
,
Если вы намеревались сделать расчет в unsigned long
точность, вам нужно сделать один из операндов unsigned long
; например сделать ((unsigned long)chr) << 24
, (Заметка: 24ul
не будет работать, тип правого операнда <<
или же >>
не влияет на левый операнд).
С chr = 234
, выражение chr << 24
оценивается изолированно: chr
повышен до (32-разрядная подпись) int
и сдвинул влево на 24 бита, получив отрицательный int
значение. При назначении на 64-битную unsigned long
знаковый бит распространяется через старшие 32 бита 64-битного значения. Обратите внимание, что метод расчета chr << 24
не зависит от того, чему присвоено значение.
Когда сдвиг составляет всего 8 бит, результатом является положительное (32-разрядное знаковое) целое число, и этот знаковый бит (0) распространяется через наиболее значимые 32-разрядные числа unsigned long
,