简体   繁体   English

如何安全地比较两个无符号整数计数器?

[英]How to safely compare two unsigned integer counters?

We have two unsigned counters, and we need to compare them to check for some error conditions: 我们有两个未签名的计数器,我们需要对其进行比较以检查某些错误情况:

uint32_t a, b;
// a increased in some conditions
// b increased in some conditions
if (a/2 > b) {
   perror("Error happened!");
   return -1;
}

The problem is that a and b will overflow some day. 问题是ab会有一天会溢出。 If a overflowed, it's still OK. 如果a溢出,它仍然确定。 But if b overflowed, it would be a false alarm. 但是如果b溢出,那将是一个错误的警报。 How to make this check bulletproof? 如何使这张支票防弹?

I know making a and b uint64_t would delay this false-alarm. 我知道制作ab uint64_t会延迟此错误警报。 but it still could not completely fix this issue. 但是它仍然不能完全解决此问题。

=============== ===============

Let me clarify a little bit: the counters are used to tracking memory allocations, and this problem is found in dmalloc/chunk.c : 让我澄清一下:计数器用于跟踪内存分配,此问题在dmalloc / chunk.c中找到:

#if LOG_PNT_SEEN_COUNT
  /*
   * We divide by 2 here because realloc which returns the same
   * pointer will seen_c += 2.  However, it will never be more than
   * twice the iteration value.  We divide by two to not overflow
   * iter_c * 2.
   */
  if (slot_p->sa_seen_c / 2 > _dmalloc_iter_c) {
    dmalloc_errno = ERROR_SLOT_CORRUPT;
    return 0;
  }
#endif

I think you misinterpreted the comment in the code: 我认为您误解了代码中的注释:

We divide by two to not overflow iter_c * 2 . 我们除以2不会溢出iter_c * 2

No matter where the values are coming from, it is safe to write a/2 but it is not safe to write a*2 . 无论值来自何处,写a/2都是安全的,但是写a*2是不安全的。 Whatever unsigned type you are using, you can always divide a number by two while multiplying may result in overflow. 无论使用哪种无符号类型,都可以始终将数字除以二,而乘法可能会导致溢出。

If the condition would be written like this: 如果条件是这样写的:

if (slot_p->sa_seen_c > _dmalloc_iter_c * 2) {

then roughly half of the input would cause a wrong condition. 那么大约一半的输入将导致错误的情况。 That being said, if you worry about counters overflowing, you could wrap them in a class: 话虽这么说,如果您担心计数器溢出,可以将它们包装在一个类中:

class check {
    unsigned a = 0;
    unsigned b = 0;
    bool odd = true;
    void normalize() {
        auto m = std::min(a,b);
        a -= m;
        b -= m;
    }
public:
    void incr_a(){ 
        if (odd) ++a;
        odd = !odd;
        normalize();
    }
    void incr_b(){ 
        ++b;
        normalize();
    }
    bool check() const { return a > b;}
}

Note that to avoid the overflow completely you have to take additional measures, but if a and b are increased more or less the same amount this might be fine already. 请注意,要完全避免溢出,您必须采取其他措施,但是如果将ab增加或多或少都相同,则可能已经可以了。

Note overflows as they occur. 注意溢出的发生。

uint32_t a, b;
bool aof = false;
bool bof = false;

if (condition_to_increase_a()) {
  a++;
  aof = a == 0;
}

if (condition_to_increase_b()) {
  b++;
  bof = b == 0;
}

if (!bof && a/2 + aof*0x80000000 > b) {
   perror("Error happened!");
   return -1;
}

Each a, b interdependently have 2 32 + 1 different states reflecting value and conditional increment. 每个a, b相互依赖地具有2 32 +1个不同的状态,分别反映值和条件增量。 Somehow, more than an uint32_t of information is needed. 以某种方式,不仅需要uint32_t信息。 Could use uint64_t , variant code paths or an auxiliary variable like the bool here. 可以在此处使用uint64_t ,变体代码路径或辅助变量(如bool

The posted code actually doesn't seem to use counters that may wrap around. 实际上,发布的代码似乎并没有使用可能会回绕的计数器。

What the comment in the code is saying is that it is safer to compare a/2 > b instead of a > 2*b because the latter could potentially overflow while the former cannot. 代码中的注释是说,比较a/2 > b而不是a > 2*b是更安全的,因为后者可能潜在地溢出而前者则不能。 This particularly true of the type of a is larger than the type of b . a的类型比b的类型特别大。

Normalize the values as soon as they wrap by forcing them both to wrap at the same time. 通过强制将两个值同时包装,可以在包装后立即标准化这些值。 Maintain the difference between the two when they wrap. 包裹时要保持两者之间的差异。

Try something like this; 尝试这样的事情;

uint32_t a, b;
// a increased in some conditions
// b increased in some conditions
if (a or b is at the maximum value) {
   if (a > b)
   {
     a = a-b; b = 0;
   }
   else
   {
     b = b-a; a = 0;
   }
}
if (a/2 > b) {
   perror("Error happened!");
   return -1;
}

If even using 64 bits is not enough, then you need to code your own "var increase" method, instead of overload the ++ operator (which may mess your code if you are not careful). 如果仅使用64位是不够的,那么您需要编写自己的“变量增加”方法,而不是重载++运算符(如果不小心,可能会使您的代码弄乱)。
The method would just reset var to '0' or other some meaningfull value. 该方法只会将var重置为“ 0”或其他有意义的值。

If your intention is to ensure that action x happens no more than twice as often as action y , I would suggest doing something like: 如果您要确保操作x发生的次数不超过操作y两倍,我建议您执行以下操作:

uint32_t x_count = 0;
uint32_t scaled_y_count = 0;

void action_x(void)
{
  if ((uint32_t)(scaled_y_count - x_count) > 0xFFFF0000u)
    fault();
  x_count++;
}

void action_y(void)
{
  if ((uint32_t)(scaled_y_count - x_count) < 0xFFFF0000u)
    scaled_y_count+=2;
}

In many cases, it may be desirable to reduce the constants in the comparison used when incrementing scaled_y_count so as to limit how many action_y operations can be "stored up". 在许多情况下,可能希望在递增scaled_y_count时使用的比较中减少常量,以限制可以“存储”多少个action_y操作。 The above, however, should work precisely in cases where the operations remain anywhere close to balanced in a 2:1 ratio, even if the number of operations exceeds the range of uint32_t . 但是,即使操作数量超出uint32_t的范围,上述操作也应在操作保持在接近平衡的2:1比例的情况下精确地起作用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM