Результат приведения типа и побитовой операции в C зависит от порядка - PullRequest
0 голосов
/ 28 июня 2018

Я пытался напечатать минимум int, char, short, long без использования файла заголовка <limit.h>. Так что побитовая операция будет хорошим выбором. Но случилось нечто странное.

Заявление

printf("The minimum of short: %d\n", ~(((unsigned short)~0) >> 1));

дает мне

The minimum of short: -32768

Но утверждение

printf("The minimum of short: %d\n", ~((~(unsigned short)0) >> 1));

дает мне

The minimum of short: 0

Это явление также встречается в char. Но это не происходит в long, int. Почему это происходит?

И стоит упомянуть, что я использую VS Code в качестве моего редактора. Когда я переместил курсор на unsigned char в выражении

printf("The minimum of char: %d\n", (short)~((~(unsigned char)0) >> 1));

Это дает мне подсказку (int) 0 вместо (unsigned char)0, что я и ожидал. Почему это происходит?

1 Ответ

0 голосов
/ 28 июня 2018

Прежде всего, ни один из ваших кодов не является действительно надежным и не будет выполнять то, что вы ожидаете.

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 правильный спецификатор формата, и вы получите оттуда ожидаемый результат.

...