5

我正在尝试以可移植的方式将 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)来做到这一点?

4

3 回答 3

8

如果int8_t定义(by <stdint.h>),则保证是二进制补码(by C 2018 7.20.1.1)。

通过将 in的值复制到with ,uint8_t u8可以将其重新解释为二进制补码值。(好的编译器会对此进行优化,以简单地将用作二进制补码值,而无需调用。)int8_t i8memcpy(&i8, &u8, sizeof i8);u8memcpy

于 2019-02-07T22:02:54.343 回答
2

在八位二进制补码中,符号位可以解释为位值 -2 8,当然是 -256。事实上,这正是 C 标准的特点。因此,给定一个存储在 a 中的 8 位值uint8_t,您希望将其重新解释为二进制补码整数,这是一种算术方法:

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

请注意,所有算术都是通过首先将操作数提升为 (signed) 来执行的int,因此既没有溢出(因为范围int足够大)也没有无符号算术环绕。算术结果保证在 的范围内int8_t,因此在将结果转换为该类型时也不存在溢出风险。

您会注意到此计算与您的计算之间的相似之处,但此计算通过u8 > 0x7f在算术中直接使用关系表达式(0 或 1)的结果避免了三元运算符,从而避免了任何分支,并且它省去了不必要的强制转换。(你的也不需要演员表。)

另请注意,如果您遇到一些不提供的奇怪实现int8_t(因为它char的 s 比 8 位宽,或者它signed char的 s 不使用二进制补码),那么该算术方法在计算正确值的意义上仍然有效,并且您可以确定在intor中安全地记录该值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()方法都依赖于提供 type 的实现int8_t,甚至比不提供的实现更不可能int8_t是实现提供的 anint8_t不是字符类型。

于 2019-02-07T22:38:08.160 回答
-1

两者i8 = u8;i8 = *(int8_t *)&u8;可以在任何实际存在并提供该int8_t类型的系统上工作。

它们在这两种情况下都依赖于实现定义的选择,但是没有人会使用在这些情况下没有做出明显选择的实现(在很大程度上是因为很多现有代码也依赖于这些选择)。有可移植性,然后有修改你的代码以迎合永远不会存在的系统。

于 2019-02-07T22:20:04.327 回答