通过强制转换检测未签名的环绕到签名的未定义行为?

时间:2015-08-12 13:58:09

标签: c casting undefined-behavior

我在网络协议中使用uint16_t作为序列计数器。这个计数器通常会按预期包装。当接收方获取数据包时,它会根据最近收到的数据来检查此计数器,以查看它是新数据包还是无序数据包。

比较序列号时需要考虑环绕声。因此,如果最后一个序列号是0x4000,那么从0x40010xBFFF的序列号更新,序列号从0xC0000xFFFF0x00000x3FFF的范围更小。

我目前的做法如下:

uint16_t last;
uint16_t current;
...
// read in values for last and current
...
if ((int16_t)(current - last) > 0) {
    printf("current is newer\n");
} else {
    printf("current is older (or same)\n");
}

通过减去两者并将结果视为int16_t,我可以很容易地看出哪个更大,哪个更多。因此,例如,如果当前序列号至少比最后一个序列号少5,即((int16_t)(current - last) < -5),我可以假设这不是由于正常的数据包重新排序和丢弃数据包。

我意识到签名的回绕是未定义的,但是在这种情况下,为了进行比较,我将无符号值视为已签名。这是否会调用未定义的行为,如果是这样,那么进行此类比较的更好方法是什么?

3 个答案:

答案 0 :(得分:6)

超出范围转换的行为是实现定义的

为什么不完全避免这个问题并写下:

if ( current != last && current - last < 0x8000 )
    printf("current is newer\n");
else
    printf("current is older (or same)\n");

注意:此答案仅适用于涉及uint16_t的特定问题。对于其他类型,将需要不同的代码。

答案 1 :(得分:0)

您可以将uint16_t转换为int32_t并进行减法。

if (((int32_t) current - (int32_t) last) > 0) {
    printf("current is newer\n");
} else {
    printf("current is older (or same)\n");
}

注意:当转换为int16_t时,uint16_t s大于32767将显示为负数(在有符号整数中,最高有效位设置为1表示负数,在无符号类型中它只是一个常规位)。

答案 2 :(得分:-1)

减法将使用提升类型进行,除非int为16位,在这种情况下行为是实现定义的。

为了安全起见,您可以使用三元计算绝对差值:

current > last ? current - last : last - current