3

Running the following program display -4 while 252 is expected:

unsigned char a=3;
printf ("%d", ~a);

Why this code doesn't display 252?

I also tested the folowings according to the proposed answer:

printf ("%u", ~a);

displays: 4294967292

printf ("%hu", ~a);

displays: 65532

Why ~a doesn't return an unsigned char since a is an unsigned char?

My question is not what should I do to display 252 ? My question is Why 252 is not displayed?

Mr Robot
  • 1,037
  • 9
  • 17
  • You should print with `%u` format specifier, since your variable is an unsigned char! – gsamaras Nov 26 '18 at 10:49
  • THIS IS NOT A DUPLICATE OF "How to print an unsigned char in C?" PLEASE READ THE QUESTION BEFORE MARKING AS DUPLICATE. – Mr Robot Nov 26 '18 at 10:57
  • 1
    I agree that this is not exact duplicate, and should be reopened. However, please do not use CAPS in your posts as that is considered shouting, and impolite. Also please do not include commentary on your question text. – user694733 Nov 26 '18 at 11:10
  • I understand, but I'm a bit I'm fed up of always fighting against guy that don't read the questions and mark them as duplicate. That's really really paintfull. – Mr Robot Nov 26 '18 at 11:12
  • You have a mistake in the edits you made. [`"%hu"` gives you `65532`, while `"%hhu"` should give you the expected `252`](https://ideone.com/Ny3bte). – Some programmer dude Nov 26 '18 at 11:19

3 Answers3

9

In addition to the answer of @Someprogrammerdude, here are the relevant passages from The Book1):

Unary arithmetic operators (§6.5.3.3/4)

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.

Arithmetic operands - Boolean, characters, and integers (§6.3.1.1):

  1. Every integer type has an integer conversion rank defined as follows:

    • No two signed integer types shall have the same rank, even if they have the same representation.
    • The rank of a signed integer type shall be greater than the rank of any signed integer type with less precision.
    • The rank of long long int shall be greater than the rank of long int, which shall be greater than the rank of int, which shall be greater than the rank of short int, which shall be greater than the rank of signed char.
    • The rank of any unsigned integer type shall equal the rank of the corresponding signed integer type, if any.
    • The rank of any standard integer type shall be greater than the rank of any extended integer type with the same width.
    • The rank of char shall equal the rank of signed char and unsigned char.
    • The rank of _Bool shall be less than the rank of all other standard integer types.
    • The rank of any enumerated type shall equal the rank of the compatible integer type (see 6.7.2.2).
    • The rank of any extended signed integer type relative to another extended signed integer type with the same precision is implementation-defined, but still subject to the other rules for determining the integer conversion rank.
    • For all integer types T1, T2, and T3, if T1 has greater rank than T2 and T2 has greater rank than T3, then T1 has greater rank than T3.
  2. The following may be used in an expression wherever an int or unsigned int may be used:

    • An object or expression with an integer type whose integer conversion rank is less than or equal to the rank of int and unsigned int.
    • A bit-field of type _Bool, int, signed int, or unsigned int. If an int can represent all values of the original type, the value is converted to an int; otherwise, it is converted to an unsigned int. These are called the integer promotions.48) All other types are unchanged by the integer promotions.
  3. The integer promotions preserve value including sign. As discussed earlier, whether a "plain" char is treated as signed is implementation-defined.

48) The integer promotions are applied only: as part of the usual arithmetic conversions, to certain argument expressions, to the operands of the unary +, -, and ~ operators, and to both operands of the shift operators, as specified by their respective subclauses.


Your Question:

Why ~a doesn't return an unsigned char since a is an unsigned char?

Because integer promotions apply.

unsigned char a = 3;
printf ("%d", ~a);

a is an unsigned char, a type with a range that can be represented by an int. So a gets promoted to an int. Assuming 32-bit wide ints and two's complement:

  310 = 0000 0000 0000 0000 0000 0000 0000 00112
~310 = 1111 1111 1111 1111 1111 1111 1111 11002

The result interpreted as signed int is negative because the most significant bit, the sign bit, is set.

Convert to decimal:

    1111 1111 1111 1111 1111 1111 1111 11002
 ¬ 0000 0000 0000 0000 0000 0000 0000 00112
 + 0000 0000 0000 0000 0000 0000 0000 00012
   ────────────────────────────
    0000 0000 0000 0000 0000 0000 0000 01002

01002 = 0 × 23 + 1 × 22 + 0 × 22 + 0 × 22
           1 × 22
           =   410
           = −410 (with the original sign)

~> printf() prints -4.

To get the desired result of 252 with your original code which uses "%d" as format specifier, some casting would be needed:

unsigned char a = 3;
printf("%d\n", (int)((unsigned char) ~a));  // prints 252
//              ^^^   ^^^^^^^^^^^^^
//               |          cast the result of ~a back to unsigned char *)
//               |          to discard the bits > CHAR_BIT
//               cast the char back to int to agree with the format specifier 

*)Thanks to chux who made me remember that char could be signed! A cast to (possibly signed) char would give the wrong result of -4.

To get the same result without a cast, you could use the length modifier hh:

The fprintf function (§7.19.6.1/7)

The length modifiers and their meanings are:

hh  Specifies that a following d, i, o, u, x, or X conversion specifier applies to a signed char or unsigned char argument (the argument will have been promoted according to the integer promotions, but its value shall be converted to signed char or unsigned char before printing); or that a following n conversion specifier applies to a pointer to a signed char argument.

[...]

unsigned char a = 3;
printf("%hhu\n", ~a);  // prints 252


The problem with your other attempts:

printf ("%u", ~a);

displays: 4294967292

printf ("%hu", ~a);

displays: 65532

Since ~a is an integer, it is not the correct type for the format specifier u and

The fprintf function (§7.19.6.1/9):

If a conversion specification is invalid, the behavior is undefined.248) If any argument is not the correct type for the corresponding conversion specification, the behavior is undefined.



1) ISO/IEC 9899/Cor3:2007 aka C99:TR3 aka C99

Swordfish
  • 12,971
  • 3
  • 21
  • 43
  • 1
    Detail: `~a` as an `int` with `"%u"` is UB when `~a` in not in the `unsigned` range - as it is in this case (-4). C11 §6.5.2.2 6 – chux - Reinstate Monica Nov 26 '18 at 14:29
  • @chux It would be easier if you quoted the passage you are referring to. – Swordfish Nov 26 '18 at 14:34
  • `printf("%d\n", (int)((char) ~a));` print 252 when `char` is _unsigned_ and print -4 when `char` is _signed_. Needs review. Suggest `printf("%u\n", (unsigned char) ~a);` – chux - Reinstate Monica Nov 26 '18 at 14:34
  • The whole ref is too long for a comment: C11 §6.5.2.2 6 excerpts: "If the function is defined with a type that includes a prototype, and either the prototype ends with an ellipsis (, ...) or the types of the arguments after promotion are not compatible with the types of the parameters, the behavior is undefined. . . . , except for the following cases: — one promoted type is a signed integer type, the other promoted type is the corresponding unsigned integer type, and the value is representable in both types;" – chux - Reinstate Monica Nov 26 '18 at 14:37
  • @chux, Yes, I read that before, but i am not certain what \*exactly\* you reffer to. – Swordfish Nov 26 '18 at 14:44
  • @chux *Suggest `printf("%u\n", (unsigned char) ~a);`* – agreed and changed. – Swordfish Nov 26 '18 at 14:53
  • 1
    "~a is an integer, it is not the correct type for the format specifier u" leading to UB is not sufficient. "~a is an integer, it is not the correct type for the format specifier u" **and** `~a` not in `unsigned` range is UB. – chux - Reinstate Monica Nov 26 '18 at 15:00
  • @chux disagree: [C11 6.5.2.2p7](https://port70.net/~nsz/c/c11/n1570.html#): *If the expression that denotes the called function has a type that does include a prototype, the arguments are implicitly converted, as if by assignment, to the types of the corresponding parameters, taking the type of each parameter to be the unqualified version of its declared type. **The ellipsis notation in a function prototype declarator causes argument type conversion to stop after the last declared parameter. The default argument promotions are performed on trailing arguments**.* – Swordfish Nov 26 '18 at 15:03
  • 1
    It is not a _promotion_ issue but a "the value is representable in both types" one. `printf("%u\n", 42); printf("%d\n", 42u);` are not UB. `printf("%u\n", -42);` is UB. – chux - Reinstate Monica Nov 26 '18 at 15:18
  • @chux the parameter passed is `int`. What is the other type? – Swordfish Nov 26 '18 at 15:28
  • @Swordfish: I think you want your 1) note to be "[ISO/IEC 9899:2018](https://webstore.iec.ch/publication/63478)" aka C18 – pmg Nov 26 '18 at 15:37
  • @pmg Why would i want that? – Swordfish Nov 26 '18 at 15:41
  • Because you call it "The Book" :) – pmg Nov 26 '18 at 15:42
  • @pmg Well, The (old, rusty, nasty, still relevant) Book. – Swordfish Nov 26 '18 at 15:44
  • @chux Did you give up on me? :( – Swordfish Nov 26 '18 at 21:47
  • Deeper issue than for comment exchange. Perhaps I'll post as a question later. – chux - Reinstate Monica Nov 26 '18 at 21:53
4

Because ~a is an int not a character. If you want to print it as 8-bit unsigned integer type use the format "%hhu" as in

printf("%hhu\n", ~a);

The hh prefix is fo the 8-bit part, the u for unsiged.

It might also be helpful to learn about integer promotions as well as how two's complement works for representing negative numbers.

From the link about integer promitions:

integer promotions are applied ... to the operand of the unary bitwise operator ~

Your problem is a mix of issues, discussed above.

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • Damn dude, nice answer, which makes now question the dupe I chose. What do you think? Should I remove it? – gsamaras Nov 26 '18 at 10:50
  • @gsamaras It's maybe not the perfect one, but could still be linked as related. – Some programmer dude Nov 26 '18 at 10:52
  • Are you sure that ~a is an int ? And mostly : WHY ? – Mr Robot Nov 26 '18 at 11:00
  • @Fifi For variadic functions (like `printf`) all integer arguments smaller than `int` are promoted to `int`. It's called [default argument promotion](https://en.cppreference.com/w/c/language/conversion#Default_argument_promotions). The same thing happens for `float`, it becomes promoted to `double` (which is why there's no difference between `"%f"` and `"%lf"` for `printf`). – Some programmer dude Nov 26 '18 at 11:03
  • @Some programmer dude in that case, why printf("%d",sizeof( ~a)); display 4 ? – Mr Robot Nov 26 '18 at 11:10
  • 1
    @Fifi Because on jut about all common platforms `int` is 32 bits, which happens to be 4 bytes, which is what the `sizeof` operator correctly reports. – Some programmer dude Nov 26 '18 at 11:11
  • 1
    @Fifi because `sizeof` gives 4 (bytes) for an `int` on your system. Please mind that `sizeof` yields a value of type `size_t` and you should use `"%zu"` to `*printf()` it. Applying `~` to the `int` you use `sizeof` on is irrelevant. – Swordfish Nov 26 '18 at 11:11
  • 1
    @Fifi Oh and to be really correct, to print the result of `sizeof` you should use the `z` prefix, as in `"%zu"` (since `size_t`, which is the type of the result of `sizeof`, is unsigned). See e.g. [this `printf` (and family) reference](https://en.cppreference.com/w/c/io/fprintf). – Some programmer dude Nov 26 '18 at 11:13
  • That's not the question. The question is why my unsigned char is transformed to int by the operator NOT (~) ? – Mr Robot Nov 26 '18 at 11:15
  • 2
    @Fifi Becauls all types smaller `int` get promoted when an operation is performed on them: [Understanding interger conversation rules](https://wiki.sei.cmu.edu/confluence/display/c/INT02-C.+Understand+integer+conversion+rules) – Swordfish Nov 26 '18 at 11:19
  • 1
    @Fifi Please *read the links I provide*. The one about [integer promotion](https://en.cppreference.com/w/c/language/conversion#Integer_promotions) includes the text "integer promotions are applied ... to the operand of the unary bitwise operator `~`" – Some programmer dude Nov 26 '18 at 11:23
  • @Swordfish this is the answer I expected. Could you post your comment as an answer, I will vote for and mark as best answer. – Mr Robot Nov 26 '18 at 11:27
  • @Fifi So to get your desired result, you'd have to cast: `printf("%d\n", (int)((char unsigned)~a));` – Swordfish Nov 26 '18 at 11:27
  • @Swordfish Or use `"%hhu"`... :) – Some programmer dude Nov 26 '18 at 11:32
  • @Fifi - `sizeof( ~a))` is 4 for the same reason explained in the duplicate to your [previous quesion](https://stackoverflow.com/questions/53480031/why-the-operator-transforms-unsigned-char-to-int). You seem to have deleted that without actually trying to understand the dupe. – StoryTeller - Unslander Monica Nov 26 '18 at 13:40
  • 1
    `printf("%hhu\n", ~a);` is technically UB when `~a < 0`. It passes an `int` with a value not in the `unsigned` range where an `unsigned` is expected. – chux - Reinstate Monica Nov 26 '18 at 14:22
3

Why printf (“%d”, ~a); display -4 when a is equal to 3?
Why ~a doesn't return an unsigned char since a is an unsigned char?

unsigned char a=3;

Because ~a is an int -4, not an unsigned char 252, due to integer promotions.


Application of ~ causes an integer promotion of a before the bit-wise complement is taken.

If an int can represent all values of the original type ..., the value is converted to an int; otherwise, it is converted to an unsigned int. These are called the integer promotions.

~3, given the common 2's complement integer encoding, is -4.


On select platforms, unsigned char, int, unsigned are all the same bit-width. On those exceptional platforms, ~a may take on a value like FFFFFFFC (4294967292) as it is promoted to unsigned instead.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256