如何在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]时,测试起作用。
通常,如果您有两个无符号计数器a
和b
,其值介于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),因此对a
和b
使用任何无符号整数类型的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.
这是有效的,因为无符号减法确实是无符号加法的反转,没有不可逆的情况或可以触发的未定义行为。