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_t
dataReceived[3]
转换为int16_t
。这种转换按价值运作;代表它的位是无关紧要的。由于所有 uint8_t
值都可以在 int16_t
中表示,因此结果是 0 到 255(含)之间的值。在您询问的情况下,该值为 255。
然后 整数提升 对 <<
的左操作数执行。如果 int
在此 C 实现中是 16 位,则 int16_t
是 int
的别名或被转换为 int
。 (例外:如果 C 实现不对 int
使用二进制补码,则行为会更复杂。但是,本答案不会涉及。)然后执行移位操作。在数学上将 255 左移 8 位得到 65,280。但是,这在 16 位 int
中是不可表示的,因此行为未定义,根据 C 2018 6.5.7 4.
如果int
比16位宽,那么整数提升提升int16_t
到int
,移位不会溢出,结果是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;
}
我在 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_t
dataReceived[3]
转换为int16_t
。这种转换按价值运作;代表它的位是无关紧要的。由于所有 uint8_t
值都可以在 int16_t
中表示,因此结果是 0 到 255(含)之间的值。在您询问的情况下,该值为 255。
然后 整数提升 对 <<
的左操作数执行。如果 int
在此 C 实现中是 16 位,则 int16_t
是 int
的别名或被转换为 int
。 (例外:如果 C 实现不对 int
使用二进制补码,则行为会更复杂。但是,本答案不会涉及。)然后执行移位操作。在数学上将 255 左移 8 位得到 65,280。但是,这在 16 位 int
中是不可表示的,因此行为未定义,根据 C 2018 6.5.7 4.
如果int
比16位宽,那么整数提升提升int16_t
到int
,移位不会溢出,结果是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;
}