Why are characters with value above 127 "padded" with 1s to be 16 bits long, while ones with values 0-127 aren't? 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) &gt; 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 &gt;&gt; 8;
bar[1] = foo;
uint16_t extracted = (uint16_t((uint8_t)bar[0])) &lt;&lt; 8 | (uint8_t)bar[1];

Thank you in advance for any help you can provide.
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.