Search code examples
c++cinteger-overflowunsigned-integer

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. If a overflowed, it's still OK. But if b overflowed, it would be a false alarm. How to make this check bulletproof?

I know making a and b uint64_t would delay this false-alarm. 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:

#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

Solution

  • I think you misinterpreted the comment in the code:

    We divide by two to not overflow 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. 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.