C - 添加原始字节时的签名

C - Signedness when adding raw bytes

我在 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 位,带有一些始终为零的位)(来自 Microchip 的 PIC24F)上编程。 我希望你能解决我遇到的问题。这么看似微不足道的问题让我有点迷茫...

(int16_t)dataReceived[3] << 8中,uint8_tdataReceived[3]转换为int16_t。这种转换按价值运作;代表它的位是无关紧要的。由于所有 uint8_t 值都可以在 int16_t 中表示,因此结果是 0 到 255(含)之间的值。在您询问的情况下,该值为 255。

然后 整数提升<< 的左操作数执行。如果 int 在此 C 实现中是 16 位,则 int16_tint 的别名或被转换为 int。 (例外:如果 C 实现不对 int 使用二进制补码,则行为会更复杂。但是,本答案不会涉及。)然后执行移位操作。在数学上将 255 左移 8 位得到 65,280。但是,这在 16 位 int 中是不可表示的,因此行为未定义,根据 C 2018 6.5.7 4.

如果int比16位宽,那么整数提升提升int16_tint,移位不会溢出,结果是0到65280之间的某个值,包括在内,即 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