是否通过强制转换为已签名的未定义行为来检测未签名的环绕?

Is detecting unsigned wraparound via cast to signed undefined behavior?

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

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

我目前的做法是这样的:

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),我可以假设这不是由于正常的数据包重新排序并丢弃数据包。

我知道有符号环绕是未定义的,但是在这种情况下,为了进行比较,我将无符号值视为有符号值。这是否会调用未定义的行为,如果是,执行此类比较的更好方法是什么?

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

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

current > last ? current - last : last - current

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

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

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

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

您可以将 uint16_ts 转换为 int32_ts 并进行减法。

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

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