3

I have compiled the following C Program in Code::Blocks 10.05 on Windows 7.

int main()
{
    unsigned char a=-5;
    signed char b=-5;
    char c=-5;
    printf("%d  %d  %d \n",a,b,c);
    printf("%u  %u  %u \n",a,b,c);
}

I expected the following output

251 -5 -5
251 251 251

I have reasoned like this,

-5 is represented as 1111 1011 in 2's Complement Notation. Hence 251 will be printed.

But I got the following output.

251 -5 -5 
251 4294967291 4294967291

I know 4294967291 is the 32 bit 2's Complement representation of -5.

But why the unsigned char a is printed as 251 instead of 4294967291?

Deepu
  • 7,592
  • 4
  • 25
  • 47
  • 1
    `printf()` is variadic; it's arguments are promoted – as part of usual arithmetic conversions – to `int` if they are narrower than `int`. See e.g. the [relevant C part of cppreference](http://en.cppreference.com/w/c/language/conversion) – The Paramagnetic Croissant Aug 01 '15 at 06:12
  • 1
    You must match the format specifier to the type. `%d` should be used for all character types. (There are other specifiers but they are unnecessary). `%u` can only be used with `unsigned char` (out of the character types). Your `%u` line causes undefined behaviour. – M.M Aug 01 '15 at 06:29
  • 1
    This is quite similar to http://stackoverflow.com/questions/7084857/what-are-the-automatic-type-promotions-of-variadic-function-arguments but I am not sure if it is a dupe. The two questions lead to similar results but are worded quite differently. – Vality Aug 01 '15 at 06:30

4 Answers4

4

printf is a variadic function, which causes (thanks to @TheParamagneticCroissant for the correction) the integral arguments to become promoted to int or unsigned int. Which according to the standard promotion rules will sign extend them appropriately. This is causing the numbers you are getting to be the results after this promotion process.

Instead you can use "%" PRIu8 to print 8 bit unsigned and "%" PRId8 to print 8 bit signed numbers. However beware that the arguments will still be promoted and then printf will downcast them again according to the format specifier, which may possibly still change that value but I am not sure of that from memory.

In addition char is not guaranteed in C to be 8 bit, it is better to include the <stdint.h> header and use the proper fixed width types.

A fixed version of your code would be as so:

#include <stdint.h>
int main()
{
    uint8_t a=-5;
    int8_t b=-5;
    printf("%" PRId8 "%" PRId8 "\n", a, b);
    printf("%" PRIu8 "%" PRIu8 "\n", a, b);
}

Note I do not include an equivalent of a char as all the fixed width types are of defined sign so there is no direct equivalent of char, which is of undefined signedness.

Vality
  • 6,577
  • 3
  • 27
  • 48
  • If the downvoter would care to mention what I have done wrong I would be very grateful, I am tired and have no C compiler on me so am only coding from memory. – Vality Aug 01 '15 at 06:20
  • 2
    "The %u and %d format specifiers are used to print int sized signed and unsigned numbers. This means that your chars are being promoted to int and unsigned ints" – no, they are getting promoted regardless of the format specifier; they are being promoted because `printf()` is variadic. They are also only ever promoted to **one specific** type (which is either `int` or `unsigned int` depending on the size and range of `int` and `char`, and it is probably `int` on most modern machines.) – The Paramagnetic Croissant Aug 01 '15 at 06:20
  • 1
    thanks for fixing it; I've retracted my DV. I also suggest we find a duplicate question, I'm almost entirely sure this question has been asked several times already. – The Paramagnetic Croissant Aug 01 '15 at 06:24
  • @TheParamagneticCroissant Could you possibly take a glance at the fixed answer, I dont want to mislead the OP and am not 100% sure I am right even after the fix. – Vality Aug 01 '15 at 06:27
  • In addition I will take a look around and see if I can find a dupe – Vality Aug 01 '15 at 06:28
2

unsigned integral types (including unsigned char) work with modulo arithmetic. For unsigned char, that is modulo UCHAR_MAX + 1, where UCHAR_MAX is an implementation-defined value in <limits.h> with a typical value with most implementations of 255 (255 is also the minimum value that the standard permits for UCHAR_MAX). Adding 256 to -5 (i.e. to get a remainder between 0 and 255 in order to initialise a in your code) will explain why a has a value of 251.

As to why any of the values are printing at all, the values are being promoted to be of type int when passed to printf(). This promotion is unrelated to your format string but is what the %d format specifier expects, so the values print as you see.

Peter
  • 35,646
  • 4
  • 32
  • 74
1

@Vality is slightly wrong in his answer. Because unsigned char must hold values up to at least 255, then if uint8_t exists, CHAR_BIT must be 8.

There's no reason to use the nasty PRI macros, just use this:

unsigned char a = -5;
signed char b = -5;
char c = -5;

printf("%hhd  %hhd  %hhd \n", a, b, c);
printf("%hhu  %hhu  %hhu \n", a, b, c);
o11c
  • 15,265
  • 4
  • 50
  • 75
  • Im not quite sure about that, char I believe is allowed to be larger (say 12 bits), and uint8_t be another discrete type which is not otherwise given a name. Though I have not got a standard in front of me so cannot say for certain if that is permitted and if there any systems that do it. However if you do do it that way I would strongly suggest putting in an explicit check of CHAR_BIT and an #error. I agree though that your solution will work on all but the most bizare esoteric machines if not always. – Vality Aug 01 '15 at 06:44
  • @Vality No type can be smaller than `char` though. But I *really* don't care about those ancient systems, they won't even have a halfway-recent libc. – o11c Aug 01 '15 at 07:11
0

Because a holds the value 251, not the value -5.