9

I am new to the C-Headers - stdint.h and inttypes.h. I was trying out some code to get an inkling of how uint8_t works. But, it seems to have encountered a problem.

I have declared 4 uint8_t integers with the boundary values 0, 255, 256, -1 respectively and performed some simple arithmetic operations on it. I did this since I wanted to know what errors/warnings does the c-compiler (I am using gcc 5.4.0 on linux) generate. And if not, I was interested in knowing what the output looked like. The code in given below.

#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>

int main() {
        int a = 10;
        printf("%d\n", a++);
        printf("%d\n\n", a);

        // 8 bit unsigned integer -> range[0, 255]
        uint8_t ua81=0, ua82=255, ua83=256, ua84=-1;
        printf("--------STDINT.H----uint8_t--DEMO--------\nua81 = %" PRIu8 "\n", ua81);
        printf("ua82 = %" PRIu8 "\nua83 = %" PRIu8 "\nua84 = %" PRIu8 "\n\n", ua82, ua83, ua84);
        printf("ua81+1 = %" PRIu8 "\nua82-3 = %" PRIu8 "\nua83-4+7 = %" PRIu8 "\nua84-1+20 = %" PRIu8 "\n----------\n\n", ua81+1, ua82-3, ua83-4+7, ua84-1+20);

        return 0;
}

The output of this code is as follows:

vagrant@ubuntu-xenial:~/Documents/Coding Practice/p_c$ vi stdint_h.c
vagrant@ubuntu-xenial:~/Documents/Coding Practice/p_c$ gcc -Wall stdint_h.c -o a
stdint_h.c: In function ‘main’:
stdint_h.c:11:33: warning: large integer implicitly truncated to unsigned type [-Woverflow]
  uint8_t ua81=0, ua82=255, ua83=256, ua84=-1;
                                 ^
vagrant@ubuntu-xenial:~/Documents/Coding Practice/p_c$ ./a
10
11

--------STDINT.H----uint8_t--DEMO--------
ua81 = 0
ua82 = 255
ua83 = 0
ua84 = 255

ua81+1 = 1
ua82-3 = 252
ua83-4+7 = 3
ua84-1+20 = 274
----------

As mentioned earlier, I am using a Linux machine for this with the gcc 5.4.0 compiler.

vagrant@ubuntu-xenial:~/Documents/Coding Practice/p_c$ gcc --version
gcc (Ubuntu 5.4.0-6ubuntu1~16.04.11) 5.4.0 20160609
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

I am not able to understand why, for the variable ua84, the value is not rolling over to 0 after reaching 255, while the same is working for ua83. I think the value, considering the rollover after 255, of ua84 should have been 18.

I feel this has got something to do with the Implicit Truncation mentioned in the warning that was generated pertaining to the variable ua83 while compiling. I don't know exactly what is wrong with that. I would also like to know how to go about that I still want to continue using uint8_t.

TejasKhajanchee
  • 103
  • 2
  • 8
  • What glibc version are you using? Printing `274` with PRIu8 looks like a problem with printf implementation, it shouldn't do that. – KamilCuk May 09 '19 at 08:31
  • It is very fishy that your implementation prints 274 when you used the `PRIu8` specifier. Sounds like a library bug to me. – Lundin May 09 '19 at 08:32
  • I am using `GLIBC 2.23` – TejasKhajanchee May 09 '19 at 08:35
  • 2
    @Lundin `PRIu8` might be `d` on normal systems, the requirement is that it correctly print an argument of (pre-promoted) type `uint8_t`. You could say that OP violated the contract of `PRIu8` by supplying `274` as argument. – M.M May 09 '19 at 08:36
  • @M.M, Thanks. But what you say should work for `ua83` also. In case of `ua83`, it seems to be working fine. @KamilCuk and @Lundin I also was about to agree with you regarding the `bug` thing. But, I just tried it out on an `Ubuntu 18.04` machine also with `gcc-7.3.0` and `glibc-2.27`. It is still giving the same results. – TejasKhajanchee May 09 '19 at 08:47
  • @TejasKhajanchee `uint8_t ua83=256;` causes it to be initialized to `0` (the compiler even gives you a warning reminder) – M.M May 09 '19 at 08:49
  • @Lundin no it's integer promotion + printf variable argument/integer promotion. If you cast to `uint8_t` the value wraps – Jean-François Fabre May 09 '19 at 08:52
  • @Jean-FrançoisFabre I posted an answer. I still think this is a needlessly bad quality of implementation, even if standard compliant. – Lundin May 09 '19 at 08:54
  • I don't see it like that: printf "integer promotion" allows to print uint8, and adding to integer promotes the integer. So both "errors" are compensated. – Jean-François Fabre May 09 '19 at 08:56
  • @M.M, yes I agree with you. If `ua83` is assigned to be `0` as per `uint8_t ua83=256;`, I tried and observed that an operation of the form `ua83-1` makes its value to be equal to `255`. This implies that **backward rollover** is working well. An operation `ua83-4+7` gives the value `3`, implying that the **backward rollover** as well as the **forward rollover** is working fine. But an operation `--ua83+5` gives a value `260`. This result is again following the trend of `ua84`. I am still not sure what is happening. – TejasKhajanchee May 09 '19 at 09:06
  • 3
    @TejasKhajanchee The expression `ua83-1` has value `-1` and type `int`. The expression `--ua83` has value `255`. Maybe you don't understand the pre-decrement operator (it decrements in-place and resolves to the same value which was stored) – M.M May 09 '19 at 09:13

3 Answers3

11

Because of the implicit promotions, ua84-1+20 will be an int and it will have the value 274. You should explicitely convert the result to uint8_t to have it to become 18.

printf does not come with enough magic to convert its arguments depending on the format(*). It only expects that it receives what it needs and just extract the passed parameters. So here you should write:

    printf("ua81+1 = %" PRIu8 "\nua82-3 = %" PRIu8 "\nua83-4+7 = %" PRIu8 "\nua84-1+20 = %"
            PRIu8 "\n----------\n\n", (uint8_t)(ua81+1), (uint8_t)(ua82-3), (uint8_t)(ua83-4+7),
            (uint8_t)(ua84-1+20));

(*) More exactly, it is a problem with the format specifier. In paragraph 7.21.6.1 The fprintf function §7 draft n1570 for C11 says:

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); ...

So, if you have explicitely used %hhu, you would have got 18 (even if a 274 had been passed). But unfortunately the macros from inttype.h are only required to correctly display a value of the expected range, so many implementations translate PRIu8 as u (thank to Eric Postpischil for noticing that).

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • 2
    So what should `printf("300 using PRIu8: %" PRIu8 "\n", 300);` print? (https://www.ideone.com/IWhtRT). It's UB IMO. – Jabberwocky May 09 '19 at 08:40
  • 3
    @Jabberwocky: IMHO it is not defined by standard, because you are not passing a correct value. `PRIu8` is only supposed to correctly display any value in [0,255] range. What happens for other values is implementation defined at best. – Serge Ballesta May 09 '19 at 08:45
  • 1
    Re “`printf` does not come with enough magic to convert its arguments depending on the format”: C 2018 7.21.6.1 7 says it does. E.g., for `hh`, it says “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”. However, the specification for the macros of `inttypes.h` fails to say this. – Eric Postpischil May 09 '19 at 10:33
  • @EricPostpischil: you are perfectly right. I have edited my post. – Serge Ballesta May 09 '19 at 11:34
2

The expression ua84-1+20 causes the variable ua84 to undergo something called integer promotion: It's turned into an int, and the whole expression then evaluates to an int.

Then it gets printed as an unsigned int - you'll likely see that PRIu8 is defined as "u", which of course expects unsigned int (And gets even if passed an uint8_t due to how variadic functions have their integer arguments promoted (uint8_t values get promoted to int, and then that gets treated as an unsigned int by printf())).

Details: https://en.cppreference.com/w/c/language/conversion

Shawn
  • 47,241
  • 3
  • 26
  • 60
  • 3
    `you'll likely see that PRIu8 is defined as "i"` - no, I would expect `%hhu` . Whoa, indeed it's `"u"` in glibc [inttypes.h](https://github.com/lattera/glibc/blob/master/sysdeps/generic/inttypes.h#L102) – KamilCuk May 09 '19 at 08:35
  • @KamilCuk Not with glibc. – Shawn May 09 '19 at 08:36
  • If `PRIu8` is defined as `"%i` then it's a library bug. Indeed it should be `%hhu`. – Lundin May 09 '19 at 08:36
  • 1
    Brainfart; it's actually `"u"`. Updated. – Shawn May 09 '19 at 08:37
  • No, `printf` expects to receive an `int` due to default argument promotion. – Lundin May 09 '19 at 08:50
  • 1
    @Lundin Not a bug. The standard says that length modifiers are optional, as long as the macros expand to suitable specifiers for their type. `%u` is certainly suitable for printing an `uint8_t`. – Shawn May 09 '19 at 08:50
2

printf family of functions is very dangerous and unstable, with non-existent type-safety. So if you provide an argument which is not of the type you specified with the format specifier, you invoke undefined behavior. This is what the programmers of the library will tell you.

The expression ua84-1+20 results in implicit type promotion to int, see Implicit type promotion rules. So you lie to printf with PRIu8, telling it to expect an uint8_t while you pass an int.

It can be fixed by converting back to the expected type: (uint8_t)(ua84-1+20), which will print 18 as expected.

Notably, all variadic functions like printf also have a similar kind of implicit type promotion known as "the default argument promotions". These will actually convert the parameter to an int no matter what you do. But the library is expecting that promotion to happen, so when you type PRIu8 followed by (uint8_t)whatever, they expect to deal with (int)(uint8_t)whatever internally.

However, it is mighty fishy that the library prints 274, UB or not. This suggests that the printf implementation is actually not converting the parameter back to uint8_t internally. How the library can justify that is a good question, because as we can see it leads to bugs and less rugged code.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • *"This suggests that the printf implementation is actually not converting the parameter back to uint8_t internally."* I don't see evidence that `%hhu` or `%hhd` is working incorrectly. Link by KamilCuk on other answer suggests the issue is more likely in `PRIu8` definition. – user694733 May 09 '19 at 09:20
  • @user694733 Either `printf("%hhu", 256);` or `printf("%"PRIu8, 256);` prints 256 on any gcc implementation I try, both with glibc and on Mingw/Microsoft. Yes it is undefined behavior, but the library could have been written in more rugged ways by always casting the parameter to `unsigned char` internally. – Lundin May 09 '19 at 09:29