6

Почему возникает ошибка при выводе результата унарной операции ~?
(должно было ведь получиться ~a = 0)

#include <stdio.h>

int main (void)
{
    unsigned char a = 0xff;

    printf("a = %u\n", a);
    printf("~a = %u\n", ~a);

    return 0;
}

Результат (компилятор - Clang) :

a = 255
~a = 4294967040
Vitaly
  • 559
  • 1
    Где-то идёт неявное преобразование char в int (правда, где именно, я не в курсе) – andreymal Dec 09 '17 at 13:00
  • 1
    Дело всё в том, что тип unsigned char подвергается целочисленному продвижению (integral promotion) и преобразуется либо в int, либо в unsigned int (это в том случае, если int не может представить все значения unsigned char). Таким образом, при, например, 8-битном unsigned char и 32-битном int кажется, что происходит инверсия последовательности битов: 11111111, а на самом деле происходит инверсия такой последовательности битов: 00000000 00000000 00000000 11111111. – wololo Dec 09 '17 at 15:07
  • 2
    После инверсии вы получаете последовательность битов 11111111 11111111 11111111 00000000 и именно эта последовательность битов и передаётся в функцию printf. А за счёт спецификатора типа %u функция printf интерпретирует данную последовательность битов как unsigned int, т.е. как числовое значение 4294967040. – wololo Dec 09 '17 at 15:07
  • 2
    Также рекомендую ознакомится с ответами к этому вопросу. – wololo Dec 09 '17 at 15:09

2 Answers2

4

Результат ~a имеет тип int, и вычисляется обращением битов после превращения a в int (integer promotions, так как int может представить все значения unsigned char, если не рассматривать редкий случай когда UCHAR_MAX > INT_MAX). В с11 (n1570):

The result of the ~ operator is the bitwise complement of its (promoted) operand (that is, each bit in the result is set if and only if the corresponding bit in the converted operand is not set). The integer promotions are performed on the operand, and the result has the promoted type. If the promoted type is an unsigned type, the expression ~E is equivalent to the maximum value representable in that type minus E

К примеру, на одной из возможных реализаций:

(~a) -> ~(FF) -> ~(00 00 00 FF) -> FF FF FF 00 = -256

Затем %u интерпретирует int как unsigned int, поэтому для примера выше: printf() получает -256 (FF FF FF 00) и выводит 4294967040 (34 32 39...).

0 вы получите, если (~a) назад в unsigned char превратить:

unsigned char b = ~a;

в этом случае -256 в ноль превращается (§6.3.1.3/2) так как вычисления по модулю UCHAR_MAX+1 происходят (UCHAR_MAX=255 здесь). printf("%u", b) выведет 0.

Аналогично, в случае с %hhu: printf() получает int (FF FF FF 00) и интерпретирует его как unsigned char для печати:

#include <stdio.h>

int main(void) {
    unsigned char a = 0xff, b = ~a;
    printf("a=%d b=%d ~a=%d\n", a, b, ~a);
    printf("a=%u b=%u ~a=%u\n", a, b, ~a);
    printf("a=%hhu b=%hhu ~a=%hhu\n", a, b, ~a);
}

Результат

a=255 b=0 ~a=-256
a=255 b=0 ~a=4294967040
a=255 b=0 ~a=0
jfs
  • 52,361
  • @mega как я вам уже сказал. %hhu это часть Си стандарта, любой Си компилятор обязан её поддерживать и нет нужды указывать какой компилятор используется (к примеру я не знаю какой компилятор использует pythontutor откуда я результаты скопировал). Если ваш компилятор не поддерживает стандартную функциональность, то можно задать отдельный вопрос: "как эмулировать %hhu" – jfs Dec 09 '17 at 18:09
  • "к примеру я не знаю какой компилятор использует pythontutor откуда я результаты скопировал" -- вообще-то он Вам пишет и компилятор и версию (как раз для таких случаев - gcc4.8). A я просто Вам покажу то же, что и @Harry: https://i.stack.imgur.com/bOYBF.png (vs2013) – mega Dec 09 '17 at 18:20
  • 1
    Так исторически сложилось, и ms этого не скрывает (правда - и не аргументирует): "The hh, j, z, and t length prefixes are not supported" – mega Dec 09 '17 at 18:41
  • У Вас тут много комментариев, и я в них тоже особого смысла не вижу. Я просто обратил внимание остальных на то, что код в обоих ответах не даст ожидаемый вывод в VS. И по ссылке можно почитать документацию, которая это подтверждает, если кого-то конечно интересует "что там ms делает с %hhu". – mega Dec 09 '17 at 19:13
  • Можно еще добавить в примерах, что printf("%u", (unsigned char)~a); тоже выведет ожидаемый 0 – avp Dec 09 '17 at 20:06
  • 1
    @mega, %u предполагает, что в качестве аргумента был передан, как это ни странно unsigned int. В примере @avp последовательность преобразований такая: a расширяется до int, затем делается инверсия битов, затем int явно приводится к unsigned char, затем unsigned char посредством integer promotions расширяется до int для передачи в printf, затем printf интерпретирует этот int как unsigned int и выводит. Собственно всё :) – wololo Dec 09 '17 at 20:33
  • @wololo, дело не в том, что это работает, когда есть поддержка неявных преобразований, а в том, что нехорошо передавать в функцию char, когда ожидается int. – mega Dec 09 '17 at 20:39
  • Комментарии не предназначены для расширенной дискуссии; разговор перемещён в чат. – Qwertiy Dec 10 '17 at 10:54
  • @mega, нет, передавать char вместо int вполне нормально, поскольку при передачи аргументов в функции char всегда расширяется до int, поэтому разницы никакой. – Qwertiy Dec 10 '17 at 10:56
  • Нет, @Qwertiy, передавать char, когда ожидается int - уже ненормально, это вызывает лишние вопросы, которых в ответе быть не должно. – mega Dec 10 '17 at 13:53
3
printf("~a = %u\n", ~a);

%u говорит о том, что вы хотите вывести unsigned int. Так что вы получаете приведение к этому типу с последующей инверсией битов. Хотите получить 0? Тогда делайте так:

unsigned int a = 0xffffffff;

printf("a = %u\n", a);
printf("~a = %u\n", ~a);

Или, если нужен один байт - используйте верный префикс - %hhu:

unsigned char a = 0xff;

printf("a = %hhu\n", a);
printf("~a = %hhu\n", ~a);
Harry
  • 221,325
  • "используйте верный префикс - %hhu" -- он не везде поддерживается, в виндах, например, его нет – mega Dec 09 '17 at 17:25
  • @mega: %hhu это стандартный Си. – jfs Dec 09 '17 at 17:29
  • %hhu не поддерживается в vs – mega Dec 09 '17 at 17:30
  • 3
    ~a уже имеет тип int и поэтому ~a != 0, а %u только превращает int в unsigned int здесь. – jfs Dec 09 '17 at 17:30
  • @mega Ну, у меня в VC++ поддерживается... – Harry Dec 09 '17 at 17:46
  • printf("%hhu", -1); Если выводится 255, значит поддерживается, а если 65535 - нет. VS поддерживает только %hu. – mega Dec 09 '17 at 17:53
  • @mega http://i100.fastpic.ru/big/2017/1209/18/233305f6b084f75c4cda9708b1c99618.jpg - устроит? или вам надо TeamViewer'ом к себе на машину пустить и показать? :) – Harry Dec 09 '17 at 18:03
  • Да нет, верю. Вот Вам аналогичный скрин: https://i.stack.imgur.com/dgYlh.png (vs2013), аналогично будет в 2010, 2008, 2005 – mega Dec 09 '17 at 18:11