Переносимая реинтерпретация uint8_t как int8_t и форсирование двух - PullRequest
0 голосов
/ 08 февраля 2019

Я пытаюсь переосмыслить uint8_t как int8_t (и обратно) способом, который является переносимым.Я получаю по последовательному каналу, который храню в буфере uint8_t, но как только я узнаю, что это за пакет, мне нужно интерпретировать некоторые байты как комплимент двух, а другие как беззнаковые.

Я знаю, что это будет работать на многих компиляторах:

int8_t i8;
uint8_t u8 = 0x94;

i8 = (int8_t)u8;

Но это не гарантирует работу, когда u8> 127, потому что приведение значения, превышающего INT8_MAX к int8_t, не определено (я думаю).

Лучшее, что я смог придумать, это

int8_t i8;
uint8_t u8;

i8 = (u8 > INT8_MAX) ? (int8_t)(-(256-u8)):(int8_t)u8;

Это должно всегда работать, потому что вычитание всегда будет вызывать автоматическое повышение до int и никоим образом не зависит от базовогопредставления.Это неявно заставляет двоичную интерпретацию значений, превышающих INT8_MAX.

Есть ли лучший способ (или стандартный MACRO) сделать это?

Ответы [ 3 ]

0 голосов
/ 08 февраля 2019

И i8 = u8;, и i8 = *(int8_t *)&u8; будут работать в любой системе, которая действительно существует и предлагает тип int8_t.

Они полагаются на определенные в реализации варианты в обоих случаях, но никто не будет использовать реализациюэто не делало очевидного выбора в этих случаях (в немалой степени, потому что существующий код также полагается на эти варианты).Есть переносимость, а затем искажение вашего кода для обслуживания систем, которые никогда не будут существовать.

0 голосов
/ 08 февраля 2019

В дополнении к восьмибитному двоичному знаковый бит можно интерпретировать как имеющий значение места -2 8 , что, конечно, -256.Это на самом деле именно так, как его характеризует стандарт C.Поэтому, учитывая 8-битное значение, хранящееся в uint8_t, которое вы хотите интерпретировать как целое число дополнения до двух, это арифметический способ сделать это:

uint8_t u8 = /* ... */;
int8_t  i8 = (u8 & 0x7f) - (u8 > 0x7f) * 0x100;

Обратите внимание, что вся арифметикавыполняется сначала повышением операндов до (подписи) int, поэтому нет переполнения (потому что диапазон int достаточно велик для этого), а также арифметического обхода без знака.Арифметический результат гарантированно находится в диапазоне int8_t, поэтому нет риска переполнения при преобразовании результата в этот тип.

Вы заметите сходство между этим вычислением и вашим,но этот позволяет избежать троичного оператора, используя результат реляционного выражения u8 > 0x7f (0 или 1) непосредственно в арифметике, тем самым избегая любого ветвления, и он обходится без ненужных приведений.(Ваши также не нуждаются в приведениях.)

Обратите также внимание, что если вы столкнетесь с какой-то странной реализацией, которая не обеспечивает int8_t (потому что ее char шире, чем 8 бит, или ееsigned char s не используют дополнения до двух), тогда этот арифметический подход все еще работает в смысле вычисления правильного значения, и вы можете быть уверены в безопасной записи этого значения в int или short.Таким образом, абсолютно наиболее переносимым способом извлечения значения из интерпретации uint8_t для 8-битного двоичного числа будет

uint8_t u8 = /* ... */;
int i8 = (u8 & 0x7f) - (u8 > 0x7f) * 0x100;

В качестве альтернативы, если вы готовы положиться на int8_tбыть типом символа - т.е. псевдонимом для char или signed char - тогда совершенно стандартно делать эту работу следующим образом:

uint8_t u8 = /* ... */;
int8_t  i8 = *(int8_t *)&u8;

Это одинЕще более вероятно, что компилятор будет оптимизирован, чем альтернатива memcpy(), представленная в другом ответе, но, в отличие от альтернативы memcpy, эта формально имеет неопределенное поведение, если int8_t оказывается не длябыть типом персонажа.С другой стороны, и этот, и memcpy() подход зависят от реализации для предоставления типа int8_t, и даже более маловероятно, чем реализация, не обеспечивающая int8_t, состоит в том, что реализация предоставляет int8_t, который не может бытьтип символа.

0 голосов
/ 08 февраля 2019

Если определено int8_t (<stdint.h>), оно гарантированно будет дополнением до двух (согласно C 2018 7.20.1.1).

Значение в uint8_t u8 можно интерпретировать как двойноедополнить значение, скопировав его в int8_t i8 с memcpy(&i8, &u8, sizeof i8);.(Хорошие компиляторы оптимизируют это, чтобы просто использовать u8 в качестве значения дополнения до двух, без вызова memcpy.)

...