Почему добавление ведет себя по-разному через printf?

Я читал главу о побитовых операторах, я наткнулся на 1 оператор-оператор дополнения и решил запустить его на Visual С++.

int main ()
{
   unsigned char c = 4, d;
   d = ~c;
   printf("%d\n", d);
}

Он дает действительный вывод: 251

Затем вместо использования d в качестве переменной для хранения значения ~c я решил напрямую распечатать значение ~c.

int main ()
{
   unsigned char c=4;
   printf("%d\n", ~c);
}

Он выводит -5.

Почему он не работал?

Ответ 1

В этом утверждении:

printf("%d",~c);

c преобразуется в int 1 type перед тем, как применяется ~ (побитовое дополнение). Это происходит из-за целых рекламных акций, которые вызывается в операнд ~. В этом случае объект типа unsigned char продвигается до (подписанный) int, который затем (после ~ оценки оператора) используется функцией printf с соответствующим спецификатором формата %d.

Обратите внимание, что продвижение аргументов по умолчанию (так как printf является вариационной функцией) не играет никакой роли здесь, поскольку объект уже имеет тип int.

С другой стороны, в этом коде:

unsigned char c = 4, d;
d = ~c;
printf("%d", d);

выполняются следующие шаги:

  • c является объектом целочисленных рекламных акций из-за ~ (таким же образом, как описано выше).
  • ~c Значение rvalue оценивается как (подписанное) int значение (например, -5)
  • d=~c делает неявное преобразование от int до unsigned char, поскольку d имеет такой тип. Вы можете думать об этом так же, как d = (unsigned char) ~c. Обратите внимание, что d не может быть отрицательным (это общее правило для всех неподписанных типов).
  • printf("%d", d); вызывает объявления по умолчанию, поэтому d преобразуется в int и сохраняется (неотрицательное) значение (т.е. тип int может представлять все значения типа unsigned char).

1), предполагая, что int может представлять все значения unsigned char (см. TC comment ниже), но это очень вероятно таким образом. Более конкретно, мы предполагаем, что INT_MAX >= UCHAR_MAX имеет место. Обычно sizeof(int) > sizeof(unsigned char) имеет место, а байт состоит из восьми бит. В противном случае c будет преобразован в unsigned int (как в подпункте C11 §6.3.1.1/p2), а спецификатор формата также должен быть изменен в соответствии с %u, чтобы избежать получения UB (C11 §7.21. 6.1/P9).

Ответ 2

char выражается в int в выражении printf перед операцией ~ во втором фрагменте. Итак c, который

0000 0100 (2 complement)  

в двоичном коде повышается до (предполагая 32-разрядную машину)

0000 0000 0000 0000 0000 0000 0000 0100 // Say it is x  

и его побитовое дополнение равно двум дополнениям значения минус один (~x = −x − 1)

1111 1111 1111 1111 1111 1111 1111 1011  

который -5 в десятичной форме в форме 2 дополнения.

Обратите внимание, что продвижение по умолчанию char c до int также выполняется в

d = ~c;

до операции дополнения, но результат преобразуется обратно в unsigned char, поскольку d имеет тип unsigned char.

C11: 6.5.16.1 Простое назначение (p2):

В простом назначении (=) значение правильного операнда преобразуется в тип выражения присваивания и заменяет значение, хранящееся в объекте, указанном левым операндом.

и

6.5.16 (p3):

Тип выражения присваивания - это тип, который должен иметь левый операнд после преобразования lvalue.

Ответ 3

Чтобы понять поведение вашего кода, вам нужно изучить концепцию "Целочисленные рекламные акции" (что происходит в вашем коде неявно до бит мудрая НЕ операция на операнде unsigned char) Как упоминалось в проекте N1570:

§ 6.5.3.3 Унарные арифметические операторы

  1. Результатом оператора ~ является поразрядное дополнение его (продвинутый) операнд (то есть каждый бит в результате устанавливается, если и только если соответствующий бит в преобразованном операнде не установлен). The целые рекламные акции выполняются в операнде, и результат продвинутый тип. Если продвинутый тип является "беззнаковым типом", выражение ~E эквивалентно максимальному значению, представленному в этом введите минус E ".

Поскольку тип unsigned char более узкий, чем (поскольку он требует меньше байтов), тип int, - неявное продвижение типа, выполняемое абстрактной машиной (компилятором) и значением переменной c, увеличивается до int в то время (до применения операции дополнения ~). Это необходимо для правильного выполнения программы, потому что ~ нужен целочисленный операнд.

§ 6.5 Выражения

  1. Некоторые операторы (унарный оператор ~, и бинарные операторы <<, >>, &, ^ и |, совместно описываемые как побитовые операторы) должны иметь операнды, которые имеют целочисленный тип. Эти операторы дают значения, зависящие от внутренних представлений целые числа, и имеют реализованные и неопределенные аспекты для подписанных типов.

Компиляторы достаточно умны, чтобы анализировать выражения, проверять семантику выражений, выполнять проверку типов и арифметические преобразования, если это необходимо. Для того, чтобы применить тип ~ on char, нам не нужно явно писать ~(int)c — (и избегать ошибок).

Примечание:

  • Значение c повышается до int в выражении ~c, но тип c по-прежнему unsigned char - его тип не указан. Не путайте.

  • Важно: результат ~ работает от int type!, проверьте ниже код (у меня нет vs-компилятора, я использую gcc):

    #include<stdio.h>
    #include<stdlib.h>
    int main(void){
       unsigned char c = 4;
       printf(" sizeof(int) = %zu,\n sizeof(unsigned char) = %zu",
                sizeof(int),
                sizeof(unsigned char));
       printf("\n sizeof(~c) = %zu", sizeof(~c));        
       printf("\n");
       return EXIT_SUCCESS;
    }
    

    скомпилируйте его и запустите:

    $ gcc -std=gnu99 -Wall -pedantic x.c -o x
    $ ./x
    sizeof(int) = 4,
    sizeof(unsigned char) = 1
    sizeof(~c) = 4
    

    Примечание: размер результата ~c совпадает с размером int, но не равен unsigned char — результатом оператора ~ в этом выражении является int! что, как упоминалось 6.5.3.3 Унарные арифметические операторы

    1. Результатом унарного оператора - является отрицание его (продвинутого) операнда. Целое число промоакции выполняются в операнде, а результат имеет продвинутый тип.

Теперь, поскольку @haccks также объяснил в своем ответе , что результат ~c на 32-битной машине и для значения c = 4:

1111 1111 1111 1111 1111 1111 1111 1011

в десятичном значении -5 — то есть вывод вашего второго кода!

В первом коде еще одна строка интересна для понимания b = ~c;, потому что b является переменной unsigned char, а результат ~c имеет тип int, поэтому для размещения значения результата от ~c до b значение результата (~ c) усечено для вставки в неподписанный тип char следующим образом:

    1111 1111 1111 1111 1111 1111 1111 1011  // -5 & 0xFF
 &  0000 0000 0000 0000 0000 0000 1111 1111  // - one byte      
    -------------------------------------------          
                                  1111 1011  

Десятичный эквивалент 1111 1011 равен 251. Вы можете получить тот же эффект, используя:

printf("\n ~c = %d", ~c  & 0xFF); 

или, как было предложено @ouah в его , используя явное литье.

Ответ 4

При применении оператора ~ к c ему присваивается значение int, результатом является также int.

Тогда

  • в первом примере результат преобразуется в unsigned char, а затем продвигается до signed int и печатается.
  • во втором примере результат печатается как signed int.

Ответ 5

Он дает op -5. почему это не сработало?

Вместо:

printf("%d",~c);

использование:

printf("%d", (unsigned char) ~c);

чтобы получить тот же результат, что и в первом примере.

~ операнд подвергается целочисленному продвижению, а аргумент аргументов по умолчанию применяется к аргументу вариационных функций.

Ответ 6

Целое продвижение по стандарту:

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