如何在C语言中检测包装计数器和大负值之间的差异

为我的愚蠢道歉,因为这是我在这个论坛上的第一篇文章。 我试图通过以下代码检测包装无符号32位计数器和大负跳转之间的区别,但编译器给出了错误:

错误:由于数据类型的范围有限,比较始终为真[-Werror = type-limits]

这是我的代码片段:

#define MAX_BACKWARD_JUMP -4294959295 //UINT_MAX - 8000 #define MIN_BACKWARD_JUMP -3600 #define MAX_FORWARD_JUMP 4800000 signed int rtpDelta; //Signed 32-bit unsigned int currRTPTs, prevRTPTs; //unsigned 32-bit rtpDelta = currRTPTs - prevRTPTs; if ((rtpDelta > MAX_BACKWARD_JUMP && rtpDelta  MAX_FORWARD_JUMP)) { printf("Delta in Timestamps too large\n",rtpDelta); } 

这里的想法是在RTP时间戳中捕获无效的大型Deltas。 我们有一个当前的TimeStamp和一个从对等RTP客户端接收的前一个Timestamp。 RTP时间戳的无效值的边界限制是-4294959295 <rtpDelta <-3600,如果Delta小于-3600且大于-4294959295则应该抛出错误,因为更接近UMAX_INT的值将被视为翻转。 我在这做错了什么?

考虑:

 unsigned int LowerBound = -3600u, UpperBound = 4800000u; unsigned int difference = currRTPTs - prevRTPTs; 

观察到,由于包装, -3600u的值为-3600u ,将是一个大的正整数。 现在,当数学差异(无溢出计算)小于-3600合理量时, difference将是一个大整数,并且它将小于LowerBound 。 此外,如果差异不会变得太大(在负方向),那么difference将保持大于UpperBound

同样,如果差异大于合理金额4,800,000, difference的值将大于UpperBound 。 如果差异不会变得太大,那么它将保持低于LowerBound

因此,在这两种情况下,当数学差异超出期望的界限(但不是太多)时, difference的值小于LowerBound并且大于UpperBound

 if (difference < LowerBound && difference > UpperBound) printf("Delta in timestamps is outside acceptable bounds.\n"); 

观察到当数学差异超过-3600u (即4,294,967,296 – 3600)或小于4,800,000 – 4,294,967,296时,这将失败。 因此,当差异在[-4,290,167,296,4,294,963,696]时,测试起作用。

通常,如果您有两个无符号计数器ab ,其值介于0和LIMIT-1 (包括两者),且数据类型能够表示2*LIMIT-1 ,则可以使用模数运算和中间的分割点:

 difference = (a + LIMIT - b) % LIMIT; if (difference <= LIMIT/2) { /* a = b + difference */ } else { /* b = a + (LIMIT - difference) */ } 

通常LIMIT为2的幂,在这种情况下,模运算符( % LIMIT )可以用二进制AND( & (LIMIT-1) )替换,这在当前处理器上要快得多。

对于C,无符号整数类型在标准中定义为具有模运算(C99,C11 6.2.5p9),因此对ab使用任何无符号整数类型的a b将产生正确的结果, LIMIT是相应的Utype_MAX宏在"limits.h"头文件中定义。 例如,

 const unsigned int d = (unsigned int)a - (unsigned int)b; if (d <= UINT_MAX/2) /* a >= b, a = b + d */ else /* a < b, b = a + UINT_MAX - (d - 1) */ 

在我看来,事情变得复杂,不必要:

 #include  #include  #include  #define COMPLICATED 0 #if COMPLICATED int main() { const unsigned MAX_BACKWARD_JUMP = std::numeric_limits::max() - 8000; const unsigned MIN_BACKWARD_JUMP = 3600; const unsigned MAX_FORWARD_JUMP = 4800000; unsigned prevRTPTs = 0; for(unsigned i = 0; i < 10; ++i) { unsigned currRTPTs = std::rand(); std::printf("previous = %10d: ", prevRTPTs); std::printf(" current = %10d: ", currRTPTs); if(currRTPTs < prevRTPTs) { // Negative unsigned rtpDelta = prevRTPTs - currRTPTs; // Why a range and no threshold? if(MIN_BACKWARD_JUMP < rtpDelta && rtpDelta < MAX_BACKWARD_JUMP) { std::printf("Invalid range: %10d\n", rtpDelta); } else { std::printf(" OK: %10d\n",rtpDelta); } } else { // Positive unsigned rtpDelta = currRTPTs - prevRTPTs; if(MAX_FORWARD_JUMP < rtpDelta) { std::printf(" Too large: %10d\n",rtpDelta); } else { std::printf(" OK: %10d\n",rtpDelta); } } prevRTPTs = currRTPTs; } } #else int main() { const unsigned MAX_JUMP = 4800000; unsigned prevRTPTs = 0; for(unsigned i = 0; i < 10; ++i) { unsigned currRTPTs = std::rand(); std::printf("previous = %10d: ", prevRTPTs); std::printf(" current = %10d: ", currRTPTs); unsigned rtpDelta = currRTPTs - prevRTPTs; if(currRTPTs < rtpDelta) { // Negative (Underflow) rtpDelta = prevRTPTs - currRTPTs; } if(MAX_JUMP < rtpDelta) { std::printf(" Too large: %10d\n",rtpDelta); } else { std::printf(" OK: %10d\n",rtpDelta); } prevRTPTs = currRTPTs; } } 

注意:所有RTPT值都是无符号的。

只有一种方法可靠地检查溢出:在计算总和之前检查。 像这样:

 unsigned old = ..., delta = ...; if((int)delta > 0) { //cast to int, because we need to interprete negative numbers here. if(old > maxvalue - delta) printf("Overflow!\n"); //No signed arithmetic, so we don't trigger undefined behavior. } else { if(old < minvalue - delta) printf("Underflow!\n"); } 

重要的是,delta出现在比较的右侧。

现在,该怎么办,如果你在增加总和之前没有访问delta? 幸运的是,您可以简单地恢复delta:

 unsigned old = ..., new = ...; unsigned delta = new - old; //Now you can proceed as above. 

这是有效的,因为无符号减法确实是无符号加法的反转,没有不可逆的情况或可以触发的未定义行为。