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

dbu*_*ush 12 c casting undefined-behavior

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

比较序列号时需要考虑环绕声.因此,如果例如最后的序列号为0x4000,然后从一个序列号0x4001,以0xBFFF较新,且从一个序列号0xC000,以0xFFFF0x00000x3FFF更小.

我目前正在这样做的方式如下:

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");
}
Run Code Online (Sandbox Code Playgroud)

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

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

M.M*_*M.M 6

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

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

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

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

  • 在 `int` 是 16 位的系统上,`current-last` 将被评估为 `uint16_t`。在 `int` 为 17 位或更大的系统上,不计算填充,`current-last` 将被评估为(有符号的)`int`。对于加法和减法,这种情况通常可以通过将和或差转换回类型“uint16_t”来补救。在极少数情况下,要计算可能超过 46,340 的两个 16 位值的乘积的低 16 位,应首先将其中一个数字乘以“1u”。给定 `uint16_t x=65533;`,`(uint16_t)(1u*x*x)` 的值为 9,因为... (2认同)
  • ...65533u*65533 == 4294574089u 和 (uint16_t)4294574089u == 9,但是当 x 为 46341 或更大时,尝试按照 `(uint16_t)(x*x)` 或 `x*x` 执行计算可能导致一些 32 位编译器将时间和因果关系的所有概念抛诸脑后。尽管看起来很疯狂,但一些编译器作者认为,由于标准允许编译器将 `if (x &lt; 65000) foo(x); x*=x;` 变成 `foo(x); x*=x;`,而后者“更高效”,他们应该尝试让他们的编译器做到这一点。如果想要`if (x &lt; 65000) foo(x); x*=x;` 必须这样写 `if (x &lt; 65000) foo(x); x*=1u*x;` (2认同)