Неподписанное и подписанное расширение

Может кто-нибудь объяснить мне следующий код:

void myprint(unsigned long a)
{
    printf("Input is %lx\n", a);
}
int main()
{
    myprint(1 << 31);
    myprint(0x80000000);
}

с gcc main.c:

Input is ffffffff80000000
Input is 80000000

Почему (1 << 31) обрабатывается как подписанный, а 0x80000000 обрабатывается как unsigned?

Ответ 1

В C результат выражения зависит от типов операндов (или некоторых из операндов). В частности, 1 является int (подписано), поэтому 1 << n также int.

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

Если у вас есть какие-либо заблуждения: буквальный 0x80000000 - большое положительное число. Люди иногда ошибочно приравнивают это к отрицательному числу, смешивая значения с представлениями.

В вашем вопросе вы говорите: "Почему 0x80000000 рассматривается как unsigned?". Однако ваш код на самом деле не полагается на подпись 0x80000000. Единственное, что вы делаете с ним, это передать его функции, которая принимает параметр unsigned long. Так что независимо от того, подписано это или нет, не имеет значения; при переходе к преобразованию он преобразуется в unsigned long с тем же значением. (Так как 0x80000000 находится в пределах минимального гарантированного диапазона для unsigned long, у него нет шансов выйти за пределы диапазона).

Итак, это 0x80000000. Что насчет 1 << 31? Если ваша система имеет 32-битный int (или более узкий), это вызывает undefined поведение из-за подписанного арифметического переполнения. (Ссылка на дальнейшее чтение). Если ваша система имеет более крупные значения, то это приведет к тому же выводу, что и строка 0x80000000.

Если вы используете 1u << 31 вместо этого, и у вас есть 32-битные int, тогда не существует поведения undefined, и вам гарантированно будет дважды видеть выход программы 80000000.

Поскольку ваш результат не был 80000000, мы можем заключить, что ваша система имеет 32-битный (или более узкий) int, и ваша программа фактически вызывает поведение undefined. Тип 0x80000000 будет unsigned int, если int - 32-разрядный или unsigned long в противном случае.

Ответ 2

Почему (1 << 31) обрабатывается как подписанный, а 0x80000000 обрабатывается как unsigned?

От 6.5.7 Операторы сдвига битов в спецификациях C11:

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

Итак, поскольку 1 является int (из раздела 6.4.4.1, упомянутого в следующем абзаце), 1 << 31 также является int, для которого значение не определено корректно в системах, где int равно меньше или равно бит 32. (Пусть даже ловушка)


От 6.4.4.1 Целочисленные константы

3 Десятичная константа начинается с ненулевой цифры и состоит из последовательность десятичных цифр. Октальная константа состоит из префикса 0 необязательно с последующей последовательностью цифр от 0 до 7. шестнадцатеричная константа состоит из префикса 0x или 0X, за которым следует последовательность десятичных цифр и буквы a (или A) через f (или F) со значениями от 10 до 15 соответственно.

и

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

Suffix   |           decimal Constant         |   Hex Constant
---------+------------------------------------+---------------------------
none     |       int                          |  int
         |       int                          |  unsigned int
         |                                    |  long int
         |       long int                     |  unsigned long int
         |                                    |  long long int
         |       long long int                |  unsigned long long int
---------+------------------------------------+---------------------------
u or U   |       unsigned int                 |  unsigned int
[...]    |       [...]                        |  [...]

Итак, 0x80000000 в системе с битами бит 32 или меньшим битом int и 32 бит или больше unsigned int является unsigned int,

Ответ 3

Вы, по-видимому, используете систему с 32-разрядными int и unsigned int.

1 вписывается в int, поэтому это signed int, 0x80000000 нет. В то время как для десятичных констант будет использоваться следующий более крупный подписанный тип, который может удерживать это значение, для шестнадцатеричных и восьмеричных констант, сначала используется соответствующий беззнаковый тип, если это соответствует. Это потому, что они обычно используются без знака. См. Стандарт C, 6.4.4.1p5 для полной матрицы значений/типов.

Для целых чисел со знаком сдвиг влево с изменением знака имеет поведение undefined. Это означает, что все ставки отключены, потому что вы находитесь за пределами спецификации языка.

Сказано, что следующая интерпретация результатов:

  • long, по-видимому, 64 бит в вашей системе.
  • int сдвинул 1 в знаковый бит, как вы могли ожидать.
  • В результате получается отрицательный int.
  • Отрицательные ints преобразуются в unsigned, так что представление с двумя дополнениями не требует каких-либо операций (просто переинтерпретация битового шаблона)
  • Когда вы используете 64-битный unsigned long, знак расширяется до верхних бит для аргумента myprint.

Как этого избежать:

  • Всегда используйте целые числа без знака при смене (например, добавьте суффикс U к целочисленным константам, где это необходимо, здесь: 1U или 0x1U).
  • Помните о стандартных преобразованиях целых чисел при использовании меньших типов, чем int.
  • В общем случае, если вам нужен определенный размер, вы должны использовать stdint.h фиксированные типы ширины. Обратите внимание, что стандартные целые типы не имеют определенной битовой ширины. Для 32 бит используйте uint32_t для переменных. Для констант используйте макросы: UINT32_C(1) (без суффикса!).

Ответ 4

Моя мысль: аргумент первого вызова 'myprint()' является выражением, поэтому его нужно вычислять во время выполнения. Поэтому компилятор должен интерпретировать его (посредством сгенерированных инструкций) как подписанный int левый сдвиг, создавая отрицательный знак int, который затем расшифровывается для заполнения long, затем интерпретируется как unsigned long. (Я думаю, что это может быть ошибка компилятора?)

В отличие от этого второй вызов 'myprint()' представляет собой твердое кодированное целочисленное константное выражение, передаваемое в подпрограмму, принимающую unsigned long как аргумент; Я думаю, что компилятор написан, чтобы предположить из этого контекста, что константное выражение уже является unsigned long из-за отсутствия открытой информации о конфликтующем типе.

Ответ 5

Исправьте меня, если я ошибаюсь. Это то, что я понял.

На моей машине, как сказал M.M, sizeof (int) = 4. (Подтверждено печать sizeof (int))

Таким образом, 1 < 31 становится (подписано) 0x80000000, поскольку 1 подписывается. Но 0x8000000 становится непознанным, поскольку он не может вписаться в подписанный int (потому что он считается положительным, а max положительным значением int может быть 0x7fffffff).

Итак, когда подписанный int преобразуется в long, тогда происходит расширение знака (расширение происходит с использованием бита знака). И когда unsigned int преобразуется, он расширяется с использованием 0.

Таким образом, в случае myprint (1 < 31) есть дополнительные 1, и это не относится ни к

1) myprint (1u < 31)

2) myprint (1 < 31), когда int > 32 бит, потому что в этом случае знаковый бит не равен 1.