0

I accidentally trigger a bug in C code, my goal is to get rightmost 16 bits as unsigned from binary string.

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

int main()
{
    char msg[] = {0x01, 0x02, 0x03, 0x04, 0x11, 0xFF};
    uint16_t val;

    #define SIZE 6
    val = (msg[SIZE-2] << 8) | msg[SIZE-1];

    printf("%u\n", val);
    return 0;
}

Above code will produce val=0xFFFF as a result(GCC), to fix, I need to change msg from signed char to unsigned char. But my question is why this will happen and what memory is doing?

Dawn16
  • 140
  • 8
  • 2
    Your problem is not related to shifting but with integer promotion. Signed integer types will use sign extension when they are converted to larger integer types. – Gerhardh Mar 29 '23 at 07:39
  • 2
    Unless you are dealing with printable characters and strings, `char` isn't the type of choice in most cases. Use `uint8_t` from `stdint.h` instead whenever you are dealing with raw memory values, protocol data etc. that is not meant to be printed as string. – Gerhardh Mar 29 '23 at 07:41
  • @Gerhardh "larger integer type" here is a uint16_t. I think the problem here is "msg[SIZE-2] << 8", what does this 8bit char msg[SIZE-2] promote to? – Dawn16 Mar 29 '23 at 07:51
  • 1
    As `msg[Size-2]` is smaller than `int` it is promoted to `int` first which will result in `0x0011` as Arduino uses 16 bit ints as far as I remember. And `msg[SIZE-1]` will be promoted to `0xFFFF`. At this point the destination type does not yet matter. Only after `|` it is converted if needed. – Gerhardh Mar 29 '23 at 07:56
  • @Gerhardh Thanks, you are right. I write a simple test, char val = 0x81; uint16_t result = (int16_t)val; the signed byte will promote to next level with signed format(filling 1 at left, 2's complement), so that the negative value will not change. If you want you could answer below, I will accept your answer. – Dawn16 Mar 29 '23 at 08:19
  • 2
    There are two issues: 1) char can be either signed or unsigned depending on compiler, the standard allows either. And 2) depending on if char is signed or not, it may or may not get sign extended during integer promotion. I linked a duplicate per issue. – Lundin Mar 29 '23 at 08:23
  • @Dawn16 The first thing you need to do is to drop the char type. Use either `int8_t` or `uint8_t` depending on if you _intend_ for the data to be signed or unsigned. As a rule of thumb, you should never do bitwise arithmetic on signed types. – Lundin Mar 29 '23 at 08:25

0 Answers0