1

Consider the following program,

#include <stdio.h>
int main()
{
    char a = 130;
    unsigned char b = 130;

    printf("a = %d\nb = %d\n",a,b);
    return 0;
}

This program will show the following output.

a = -126
b = 130

My question is how printf() function comes to know the type of a is signed and type of b is unsigned to show result like above?

melpomene
  • 84,125
  • 8
  • 85
  • 148
  • @WeatherVane Totally forgot. Thanks. – kocica Jul 26 '18 at 07:38
  • 3
    char is probly valid in the range [-128, 127], hence 130 overflows. Your compiler can warn you for this kind of issues. – hetepeperfan Jul 26 '18 at 07:39
  • 1
    The `printf` function *does not know* what you pass it. It believes the format that you tell it. Only a smart compiler can pick up errors. – Weather Vane Jul 26 '18 at 07:40
  • 1
    `char` can be `unsigned` or `signed` and its purely implementation specific, in your case though being `signed` and that's why it overflows when you print beyond `127` – Inian Jul 26 '18 at 07:41
  • 3
    `printf` doesn't know that, it believes in the format specifier `%u` or `%d` – David Ranieri Jul 26 '18 at 07:41
  • You should be using `%u` to print `unsigned char` or cast it to `int` before passing to `printf`. – Ajay Brahmakshatriya Jul 26 '18 at 08:01
  • @AjayBrahmakshatriya I don't think so. It will be passed as an `int` by the rules of default argument promotion anyways. –  Jul 26 '18 at 08:21
  • 1
    @FelixPalmen won't integer promotion take `unsigned char` to `unsigned int`? I thought integer promotions preserve signs. – Ajay Brahmakshatriya Jul 26 '18 at 08:26
  • @AjayBrahmakshatriya not true, 6.3.1.1 p2: "*If an int can represent all values of the original type (as restricted by the width, for a bit-field), the value is converted to an int; otherwise, it is converted to an unsigned int.*" <- `int` can represent all possible values of `unsigned char`. –  Jul 26 '18 at 08:51
  • @AjayBrahmakshatriya "*I thought integer promotions preserve signs*" <- sure they do, but that doesn't necessarily mean to preserve *signedness* of the type. –  Jul 26 '18 at 08:55
  • @FelixPalmen thanks! I wasn't aware of the clause int is given preference if the type can fit. Also, my bad on goofing up on "signs" and "signedness" :) – Ajay Brahmakshatriya Jul 26 '18 at 09:06

4 Answers4

5

printf() doesn't know the types, that's why you have to give a correct format string. The prototype for printf() looks like this:

int printf(const char * restrict format, ...);

So, the only argument with a known type is the first one, the format string.

This also means that any argument passed after that is subject to default argument promotion -- strongly simplified, read it as any integer will be converted to at least int -- or ask google about the term to learn each and every detail ;)

In your example, you have implementation defined behavior:

char a = 130;

If your char could represent 130, that's what you would see in the output of printf(). Promoting the value to int doesn't change the value. You're getting a negative number instead, which means 130 overflowed your char. The result of overflowing a signed integer type during conversion in C is implementation defined, the value you're getting probably means that on you machine, char has 8 bits (so the maximum value is 127) and the signed integer overflow resulted in a wraparound to the negative value range. You can't rely on that behavior!

In short, the negative number is created in this line -- 130 is of type int, assigning it to char converts it and this conversion overflows.

Once your char has the value -126, passing it to printf() just converts it to int, not changing the value.

2

The additional arguments to printf() are formatted according to the type specifier. See here for a list of C format specifiers.

https://fr.cppreference.com/w/c/io/fprintf

It's true that one would not expect b to be printed as 130 in your example since you used the %d specifier and not %u. This surprising behavior seems to be explained here.

Format specifier for unsigned char

I hope I got your question well.

Edit: I can not comment Felix Palmen's answer on account on my low reputation. default argument promotion indeed seems to be the key here, but to me the real question here besides the overflow of a is why b is still printed as 130 despite the use of the signed specifier. It can also be explained with default argument promotion but that should be made more precise.

Raphael D.
  • 778
  • 2
  • 7
  • 18
1

printf() does not know the data type of arguments. It works on format specifier you passed. The data type you are using is char (having range from -128 to +127) and unsigned char (having range from 0 to 255). Your output for a is overflowed after 127. So the output comes to -126.

BluesSolo
  • 608
  • 9
  • 28
Yash
  • 108
  • 1
  • 1
  • 7
  • char has 8 bit size. How 130 cause overflow in 8 bits – Ajith C Narayanan Jul 26 '18 at 08:05
  • @AjithcNarayanan `char` doesn't **have** to have 8 bits, but it's very common. You forgot the need for a *sign bit* in a signed type. See my answer for some more explanation. –  Jul 26 '18 at 08:07
  • Storing 130 in a char will set sign bit but it doesn’t cause any overflow and complier doesn’t show any warnings. Please check it – Ajith C Narayanan Jul 26 '18 at 08:15
  • @AjithcNarayanan try `-Wconversion` if you use gcc. overflow isn't the wording of the standard for such a conversion, but something implementation-defined **does** happen because `130` can't be represented in an (8bit) `signed char`. –  Jul 26 '18 at 08:53
1

You need to have a look at the definition of printf statement in stdio.h. You already got the answer in comment printf just write the string pointed by format to stdout.

It's variadic function and it use vargas to get all the arguments in variable-length argument list.

You This is from the glibc from the GNU version.

int __printf (const char *format, ...)
{
   va_list arg;
   int done;

   va_start (arg, format);
   done = vfprintf (stdout, format, arg);
   va_end (arg);

   return done;
}

What vfprintf does?

It just writes the string pointed by format to the stream, replacing any format specifier in the same way as printf does, but using the elements in the variable argument list identified by arg instead of additional function arguments.

More information about the vfprintf

danglingpointer
  • 4,708
  • 3
  • 24
  • 42