我的 uint8_t 数组中有传感器数据,这些数据是有符号的(当连接到 16 位值时)。 现在,我想对 N 个周期的值进行平均,这样我就必须将它们添加到一个大于 int16_t 的值,即 int32_t 值。
int32_t val_x = 0
//...
//do this n times when data is received, dataReceived is a uint8_t array
val_x += ((int16_t)dataReceived[3] << 8) | dataReceived[4];
但我感觉这可能会导致错误。假设我的值为 -1 (0xFFFF) 并将其添加到 32 位有符号值,它会将其解释为正值,对吧?或者编译器是否知道,当我转换为 int16_t 时,这是一个负值,并将在 int32_t 变量 val_x 中将其表示为 0xFFFFFFFF?我是否需要对总值(value)进行更多调整,例如
val_x += (int16_t)(((int16_t)dataReceived[3] << 8) | dataReceived[4]);
我在 16 位(或可能是带有一些始终为零的位的 24 位)(来自 Microchip 的 PIC24F)上进行编程。 我希望你能明白我遇到的问题。我对这样一个看似微不足道的问题有点迷失......
最佳答案
在 (int16_t)dataReceived[3] << 8
,uint8_t
dataReceived[3]
转换为int16_t
。这种转换是按值(value)进行的;代表它的位是不相关的。既然都是uint8_t
值可用 int16_t
表示,结果是 0 到 255 之间的值(含 0 和 255)。在您询问的情况下,该值为 255。
然后对 <<
的左操作数执行整数提升 。如果int
在此 C 实现中是 16 位,则 int16_t
是 int
的别名或者转换为int
。 (异常(exception):如果 C 实现不使用 int
的补码,则行为会更复杂。但是,本答案不会对此进行讨论。)然后执行移位操作。数学上将 255 左移 8 位得到 65,280。然而,这不能用 16 位 int
来表示。 ,因此行为未定义,根据 C 2018 6.5.7 4。
如果int
比 16 位宽,则整数提升提升 int16_t
至int
,移位不会溢出,结果是 0 到 65,280(含)之间的某个值,即 256 的倍数。
然后输入 dataReceived[4]
进行或运算将其位合并到该值中。结果是 0 到 65,535 之间的值(含 0 和 65,535)。
然后转换为 (int16_t)
被申请;被应用。如果值为 0 到 32,767,则为结果。否则,该值无法用 int16_t
表示。 。然后,根据 C 2018 6.3.13,结果是实现定义的或引发实现定义的信号。 GCC 和 Clang 将结果定义为对 216 进行换行,因此这将产生您想要的结果。其他编译器可能不会产生您想要的结果。
如果int
是16位,可以通过强制转换 dataReceived[3]
来避免移位溢出至int32_t
而不是int16_t
。那么整数提升不会改变它,并且 int32_t
值将发生变化。
但是,由于最终转换为 int16_t
时我们仍然需要处理溢出问题,还有另一种方法:
uint16_t tu = (uint16_t) dataReceived[3] << 8 | dataReceived[4];
int16_t ti;
memcpy(&ti, &tu, sizeof ti);
val_x += ti;
这个:
- 转化
dataReceived[3]
至uint16_t
所以类次不会溢出。 - 完成移位和合并并将结果存储在临时
uint16_t
中对象。 - 复制
uint16_t
的位对象进入int16_t
对象将它们重新解释为 16 位二进制补码整数。 - 将整数添加到
val_x
.
以下代码实际上是相同的,并且避免了命名临时代码,但对于新手 C 程序员来说可能不太熟悉且难以阅读:
val_x += (union { uint16_t u; int16_t i; }) { (uint16_t) dataReceived[3] | dataReceived[4] } .i;
关于C - 添加原始字节时的符号,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68914843/