Прежде всего, ни один из ваших кодов не является действительно надежным и не будет выполнять то, что вы ожидаете.
printf
и все другие функции переменной длины аргумента имеют дисфункциональную «особенность», называемую продвижения аргумента по умолчанию . Это означает, что фактический тип передаваемых параметров подвергается скрытому продвижению. Маленькие целочисленные типы (такие как char
и short
) повышаются до int
, который подписан. (И float удваивается.) Tl; dr: printf
- это просто функция.
Таким образом, вы можете разыгрывать различные мелкие целые типы, сколько захотите, в конце концов все равно будет повышение до int
. Это не проблема, если вы используете правильный спецификатор формата для намеченного типа, но вы не используете, вы используете %d
для int
.
Кроме того, оператор ~
, как и большинство операторов в C, выполняет неявное целочисленное продвижение своего операнда. См. Правила продвижения неявных типов .
.
Как говорится, эта строка ~((~(unsigned short)0) >> 1)
делает следующее:
- Возьмите литерал
0
типа int
и конвертируйте в unsigned short
.
- Неявное продвижение этого
unsigned short
обратно к int
посредством неявного целочисленного продвижения.
- Рассчитать побитовое дополнение значения
int
0
. Это 0xFF...FF
hex, -1
dec, при условии дополнения 2.
Сдвиг вправо int
на 1. Здесь вы вызываете поведение, определяемое реализацией, при смещении отрицательного целого числа. C позволяет это либо привести к логическому сдвигу = сдвигу в нулях, либо к арифметическому сдвигу = сдвигу в знаковом бите. Отличный результат от компилятора к компилятору и непереносимый.
Вы получаете либо 0x7F...FF
в случае логического сдвига, либо 0xFF...FF
в случае арифметического сдвига. В данном случае это, кажется, последнее, то есть после сдвига у вас все еще есть десятичное число -1
.
- Вы делаете побитовое дополнение
0xFF...FF
= -1
и получаете 0
.
- Вы наложили это на
short
. Все еще 0
.
- По умолчанию продвижение аргумента преобразует его в
int
. Все еще 0
.
%d
ожидает int
и поэтому печатает соответственно. unsigned short
печатается с %hu
и short
с %hd
. Использование правильного спецификатора формата должно отменить эффект продвижения аргумента по умолчанию.
Совет: изучите неявное продвижение типов и избегайте использования побитовых операторов в операндах со знаком типа.
Чтобы просто отобразить наименьшее значение дополнения 2 для различных типов со знаком, вы должны проделать некоторую хитрость с типами без знака, поскольку побитовые операции над их версией со знаком ненадежны. Пример:
int shift = sizeof(short)*8 - 1; // 15 bits on sane systems
short s = (short) (1u << shift);
printf("%hd\n", s);
Это смещает целое число без знака 1u
15 бит, а затем преобразует полученный результат в короткое, некоторым «способом, определяемым реализацией», что означает, что в двух системах комплемента вы в конечном итоге преобразуете 0x8000 в -32768.
Затем задайте printf
правильный спецификатор формата, и вы получите оттуда ожидаемый результат.