我在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?我是否需要对
这样的值进行更多的强制转换?val_x += (int16_t)(((int16_t)dataReceived[3] << 8) | dataReceived[4]);
我在16位(或可能是24位与一些始终为零的位)(PIC24F从Microchip)上编程。我希望你能理解我的问题。这样一个看似微不足道的问题把我弄糊涂了。
在(int16_t)dataReceived[3] << 8
中,uint8_t
、dataReceived[3]
转化为int16_t
。该转换按值操作;表示它的位是不相关的。因为所有uint8_t
的值都可以在int16_t
中表示,所以结果是一个从0到255的值,包括0到255。在您询问about的情况下,值是255。
则对<<
的左操作数进行整数提升。如果int
在这个C实现中是16位,那么int16_t
要么是int
的别名,要么被转换为int
。(例外:如果C实现不使用int
的二补,则行为更复杂。然而,这个答案不会涉及到这一点。)然后执行移位操作。将255左移8位数学上得到65280。然而,这在16位int
中是不可表示的,因此行为是未定义的,参见C 2018 6.5.7 4。
如果int
大于16位,则整型提升将int16_t
提升到int
,移位不会溢出,结果是0到65535(包括65535)之间的某个值,即256的倍数。
那么在dataReceived[4]
中的ORing将其位合并到该值中。取值范围为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;
不要害怕使用内联函数来使代码更清晰:
#ifdef __GNUC__
#define ALWAYS_INLINE __attribute__((always_inline))
#else
#define ALWAYS_INLINE
#endif
static inline ALWAYS_INLINE int16_t combineInt16(uint8_t *d)
{
int16_t i16;
memcpy(&i16, d, sizeof(i16));
return i16;
}
int16_t average(uint8_t *data, size_t size)
{
int32_t average = 0;
for(size_t index = 0; index < size / 2; index ++)
{
average += combineInt16(data);
data += 2;
}
return average / size;
}
https://godbolt.org/z/GcEKoGeWM