2

I am using the Arduino IDE to run and monitor, with an Arduino Nano clone. This code shows what I mean by the question:

char foo = 127;
char bar = 128;
Serial.println(String(foo, BIN));
Serial.println(String(bar, BIN));
foo = 383;
bar = 384;
Serial.println(String(foo, BIN));
Serial.println(String(bar, BIN));

prints

1111111
1111111110000000
1111111
1111111110000000

It still happens if I then cast the elements to char, but not if I cast them to uint8_t:

char foo = 127;
char bar = 128;
Serial.println(String((char)foo, BIN));
Serial.println(String((char)bar, BIN));
Serial.println(String((uint8_t)foo, BIN));
Serial.println(String((uint8_t)bar, BIN));
1111111
1111111110000000
1111111
10000000

It behaves the same if I do some arithmetic as well:

char foo = 127;
char bar = 128;
Serial.println(String(foo / 64, BIN));
Serial.println(String(bar / 64, BIN));
1
1111111111111110

Why is this? If I assign a number bigger than 255, but smaller than 384, it behaves like for numbers below 128, but above that, it does it again, so it's obvious that only the least significant 8 bits of the value are stored as expected, but in cases where (n mod 255) > 127 (so the 8th bit is 1) they are retrieved from memory together with another 8 bits, which are all 1s.

I can live without an answer to the above issue, what I really need to have confirmed is the following:

If I were to store and retrieve a uint16_t as two elements of a char[], it would always be safe (and not undefined behavior) if I did it as follows, right?:

uint16_t foo = 34952;
char bar[2];
bar[0] = foo >> 8;
bar[1] = foo;
uint16_t extracted = (uint16_t((uint8_t)bar[0])) << 8 | (uint8_t)bar[1];

Thank you in advance for any help you can provide.

4

On the AVR platform the char type happens to be signed, and it can store numbers in the range [−128, +127]. Thus, when you write

char bar = 128;

You are asking to store in bar a number that does not fit. It then gets reduced modulo 28 into the value that does fit, namely −128. For the kind of manipulations you are trying to make, I would recommend you use uint8_t instead of char.

it would be always safe (and not undefined behavior) if I did it as follows

It is indeed safe, and the casts you use are needed to make it safe.

3
  • "the char type happens to be signed" Oh, right! I forgot about that before, and I thought this was the key, but now I checked, and the results for -128 are exactly the same as for 128, it also doesn't help if I try casting it to char (either for storing or retrieving). "use uint8_t instead of char" great that you pointed that out, I shouldn't have been using char[] in the first place, but originally I was using a String and then moved over as that would terminate at the first NUL. It didn't occur to me to just use uint8_t[], but thankfully that still works with LoRa. – Boba0514 Jan 14 at 12:37
  • 1
    @Boba0514, 'Arduino language' has byte type too. it is a nicer name for uint8_t – Juraj Jan 14 at 13:17
  • 3
    @Juraj: “nicer” is subjective. Personally, the word “byte” evokes to me a unit of information storage equal to 8 bits, which could be used to store a signed number, an unsigned number, a collection of flags (that would have no meaningful interpretation as a number), or whatever one may want to put there. The name “uint8_t”, while not as nicely sounding, clearly states that it holds an unsigned integer. Note that Java has a byte data type, and it is signed. – Edgar Bonet Jan 14 at 14:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.