1

In my code there is no difference between uint16_t and uint32_t. Why?

I am using Raspbian on a RasPi with ARMv7 (32bit).

root@zentrale:/src# uname -a
Linux zentrale 4.19.42-v7+ #1219 SMP Tue May 14 21:20:58 BST 2019 armv7l GNU/Linux

This is the code:

void main()
{

        uint16_t wert1;
        uint32_t wert2;

        int i;

        wert1=2;
        wert2=2;
        for (i=0; i<33;i++)
        {
                printf("i: %2d\tLShifted wert1: %10u\t",i,wert1 << i);
                printf("RShifted wert1: %10u\t",wert1 >> i);
                printf("LShifted wert2: %10u\t",wert2 << i);
                printf("RShifted wert2: %10u\n",wert2 >> i);
        }
exit(0);
}

This is the stripped output:

i:  0   LShifted wert1:          2      RShifted wert1:          2      LShifted wert2:          2      RShifted wert2:          2
i:  1   LShifted wert1:          4      RShifted wert1:          1      LShifted wert2:          4      RShifted wert2:          1
[...]
i: 14   LShifted wert1:      32768      RShifted wert1:          0      LShifted wert2:      32768      RShifted wert2:          0
i: 15   LShifted wert1:      65536      RShifted wert1:          0      LShifted wert2:      65536      RShifted wert2:          0
i: 16   LShifted wert1:     131072      RShifted wert1:          0      LShifted wert2:     131072      RShifted wert2:          0
[...]

I would have expected the wert1 being 16bits getting to zero with the i=15 value as the name tells it is 16bits long.

Instead there is no difference in these two variables.

I found some reference of the max value of uint16_t in Raspian (see https://raspberry-projects.com/pi/programming-in-c/memory/variables)

So why is there no difference?

Thanks a lot!

Christian
  • 169
  • 8
  • 3
    The `uint16_t` variable is promoted to `int` for the arithmetic operation, and because an `int` is passed to `printf` the promotion remains. However, it is *undefined behaviour* to shift a 16-bit variable by more than 15 bits. – Weather Vane Jul 04 '19 at 06:46
  • 1
    @WeatherVane it is undefined to shift a value by more bits than its width. But since `int` is 32-bit wide then it is undefined only when it is pushed to the sign bit. But 33 is just plain evil! it does not even have a well-specified hardware meaning! Common cases include it zeroing, not doing anything, shifting by 33 mod 32 = 1 bits or generating an exception... – Antti Haapala -- Слава Україні Jul 04 '19 at 06:58
  • @AnttiHaapala Missed case: undefined to shift a value by its width - regardless of value. e.g. `0 << 32` – chux - Reinstate Monica Jul 04 '19 at 13:41

2 Answers2

2

Both operands of << will undergo integer promotions, i.e. C11 6.3.11p2:

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 (other than int or unsigned int) 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 (as restricted by the width, for a bit-field), the value is converted to an int; otherwise, it is converted to an unsigned int.

Since int on your platform is 32-bits wide, all values of uint16_t are representable by int. The uint32_t is converted to unsigned int!

Now the behaviours of both of them seem equal because GCC guarantees most of that! All signed arithmetic on all architectures that are supported by GCC use 2's complement; and additionally GCC does not consider the behaviour << on signed numbers as undefined in the cases where the sign bit is changed.

However, it is still undefined (even in GCC) what happens if the shift width is greater than or equal to the width of the operand (in this case 32 bits), so << 32 and << 33 will have undefined behaviour.

Other than that, in general the C standard says that the behaviour is undefined if a positive signed integer is left-shifted so that the sign bit changes! This happens when you shift a uint16_t left by so many bits that it will change the shift bit of int. Hence,

(uint16_t)0xFFFF << 16

has undefined behaviour on 32-bit platform, because the topmost bit is shifted to the sign bit of int, whereas

(uint32_t)0xFFFF << 16

does not, because the latter would use unsigned arithmetic. As always, a compiler can define behaviour beyond that which is required by the standard.

1

There is no difference because of Integer Promotions

You should really go through this excellent post on the subject Implicit type promotion rules

Assuming 4 byte int, before any arithmetic operation an uint16_t is converted to a signed int and then the operation is carried out.

A left shift on a negative number is undefined. But in this case, this number cannot be negative. So you will get the same output as a uint32_t

You should typecast the output of the left shift for proper operation.

Also, you are running the loop until i<33. At i==32, you will have undefined behaviour for uint32_t and at i==31 you will have undefined behaviour for signed integer for uint16_t

 printf("i: %2d\tLShifted wert1: %10u\t",i,  (uint16_t)(wert1 << i);
Rishikesh Raje
  • 8,556
  • 2
  • 16
  • 31