Тип целочисленных литералов и ~ в C
Я начинаю C, и меня смущает следующий пример, найденный в книге ответов C.
Один из способов найти размер unsigned long long в вашей системе - это ввести:
printf("%llu", (unsigned long long) ~0);
Я не знаю, почему этот синтаксис работает?
В моей системе int
- 32 бита, а long long
- 64 бита.
Я ожидал, что, поскольку 0
является константой целочисленного типа, ~0
вычисляет отрицание 32-битного целого числа, которое затем преобразуется в unsigned long long
оператором литья. В результате это должно дать 2 32 - 1.
Как бы то ни было, похоже, что оператор ~
уже знает, что он должен действовать на 64 бита?
Компилятор интерпретирует эту инструкцию как printf("%llu", ~(unsigned long long)0);
? Это не звучит правильно, так как литье и ~
имеют одинаковый приоритет.
Ответы
Ответ 1
Как-то похоже, что оператор ~ уже знает, что он должен действовать на 64 бита?
Это не оператор ~
, это литье. Вот как конверсия целых чисел выполняется в соответствии со стандартом:
6.3.1.3 Целочисленные и беззнаковые целые числа
- Когда значение с целым типом преобразуется в другой целочисленный тип, отличный от _Bool, если значение может быть представлено новым типом, оно не изменяется.
- В противном случае, если новый тип без знака, значение преобразуется путем многократного добавления или вычитания более чем максимального значения, которое может быть представлено в новом типе, пока значение не будет в диапазоне нового типа.
- В противном случае новый тип подписан и значение не может быть представлено в нем; либо результат определяется реализацией, либо генерируется сигнал, определяемый реализацией.
Значение подписанных int
~0
соответствует -1
для систем с двумя дополнительными представлениями отрицательных значений. Он не может быть представлен unsigned long long
, поэтому первая маркерная точка не применяется.
Используется вторая маркерная точка: новый тип без знака, поэтому MAX
из unsigned long long
добавляется к -1
один раз, чтобы получить результат в диапазоне unsigned long long
. Это имеет тот же эффект, что и знак расширения -1
до 64 бит.
Ответ 2
0
имеет тип int
, а не unsigned int
. Таким образом, ~0
(на машинах, которые используют два целочисленных представления, которые используются сегодня), равно -1, а не 2 32 - 1.
Предполагая, что 64-разрядный unsigned long long
, (unsigned long long) -1
равен -1
по модулю 2 64 который равен 2 64 - 1.
Ответ 3
0
является int
~0
по-прежнему является int
, а именно значением -1
.
Отбрасывание int
в unsigned long long
означает просто совпадение с типом, который printf ожидает с преобразованием llu
.
Однако значение -1, расширенное без знака long long, должно быть 0xffffffff для 4 байтов int и 0xffffffffffffffff для 8 байтов int.
Ответ 4
Согласно проекту Комитета N1570:
- Результатом оператора ~ является побитовое дополнение его (продвинутый) операнд (то есть каждый бит в результате устанавливается, если и только если соответствующий бит в преобразованном операнде не установлен). целые рекламные акции выполняются в операнде, и результат продвинутого типа. Если продвигаемый тип является "неподписанным типом", выражение ~ E эквивалентно максимальному значению, представленному в этом введите минус E ".
(одно дополнение). Какое из них относится к определению реализации, равно как и значение со знаковым битом 1 и всеми битами значений 0 (для первого двух) или со знакомным битом и всеми битами значений 1 (для одного дополнения), представляет собой ловушку или нормальное значение. В случае знака, величины и дополнения, если это представление является нормальным значением, оно называется отрицательным нулем.
Следовательно, поведение кода:
printf("%llu", (unsigned long long) ~0);
На некоторых машинах реализована реализация и не определена - не в соответствии с ожидаемым — зависят от внутренних представлений целых чисел в машине.
В соответствии с разделом 6.5.3.3, утвержденным способом написания кода будет:
printf("%llu", (unsigned long long) ~0u);
Кроме того, тип ~0u
- это unsigned int, где при отправке его в unsigned long long int
, для которого строка формата llu
. Чтобы напечатать ~0u
с помощью строки формата %u
.
Чтобы узнать основную концепцию литья типов, вы можете читать: Что конкретно представляет собой тип в C/С++?