Почему символы со значением выше 127 "дополняются" 1s длиной 16 бит, а символы со значениями 0-127 - нет?

Я использую Arduino IDE для запуска и мониторинга с помощью клона Arduino Nano. Этот код показывает, что я имею в виду под вопросом:

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

С принтами

1111111
1111111110000000
1111111
1111111110000000

Это все еще происходит, если я затем приведу элементы к char, но не если я приведу их к 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

Он ведет себя так же, если я делаю некоторую арифметику:

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

Почему это так? Если я назначаю число больше 255, но меньше 384, оно ведет себя так же, как и для чисел ниже 128, но выше этого оно делает это снова, так что очевидно, что только наименее значимые 8 бит значения хранятся как ожидалось, но в тех случаях, когда (n mod 255)> 127 (таким образом, 8-й бит равен 1) они извлекаются из памяти вместе с другими 8 битами, которые все равны 1с.

Я могу жить без ответа на вышеприведенный вопрос, но мне действительно нужно подтвердить следующее:

Если бы я хранил и извлекал uint16_t как два элемента char[], это всегда было бы безопасно (и не неопределенное поведение), если бы я сделал это следующим образом, верно?:

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];

Заранее благодарю вас за любую помощь, которую вы можете оказать.

, 👍2


1 ответ


Лучший ответ:

3

На платформе AVR тип char является подписанным, и он может хранить числа в диапазоне [-128, +127]. Таким образом, когда вы пишете

char bar = 128;

Вы просите сохранить в баре номер, который не подходит. Затем он уменьшается по модулю 28 до значения, которое действительно подходит, а именно −128. Для тех манипуляций, которые вы пытаетесь сделать, я бы рекомендовал вам использовать uint8_t вместо char.

было бы всегда безопасно (и не неопределенное поведение), если бы я сделал это следующим образом

Это действительно безопасно, и слепки, которые вы используете, необходимы, чтобы сделать его безопасным.

,

"тип char, оказывается, подписан"О, точно! Я забыл об этом раньше, и я думал, что это ключ, но теперь я проверил, и результаты для -128 точно такие же, как и для 128, это также не поможет, если я попытаюсь привести его к "char" (либо для хранения, либо для извлечения). "используйте uint8_t вместо char" здорово, что вы указали на это, я не должен был использовать char[] в первую очередь, но первоначально я использовал String, а затем перешел, так как это закончилось бы на первом NUL. Мне не пришло в голову просто использовать uint8_t[], но, к счастью, это все еще работает с Лорой., @Boba0514

@Boba0514, "Язык Arduino" тоже имеет тип байт. это более приятное имя для uint8_t, @Juraj

@Juraj: “лучше” - это субъективно. Лично для меня слово “байт” вызывает единицу хранения информации, равную 8 битам, которая может быть использована для хранения числа со знаком, числа без знака, набора флагов (которые не имели бы осмысленной интерпретации как число) или чего бы то ни было, что можно туда поместить. Имя “uint8\_t”, хотя и звучит не так красиво, ясно указывает, что оно содержит целое число _unsigned_. Обратите внимание, что Java имеет тип данных "byte", и он подписан., @Edgar Bonet