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

How to safely compare two unsigned integer counters?

本文关键字:两个 计数器 无符号整数 何安全 安全 比较      更新时间:2023-10-16

我们有两个无符号计数器,我们需要比较它们以检查一些错误条件:

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

问题是ab总有一天会溢出来。如果a溢出,它仍然可以。但如果b溢出,那将是虚惊一场。如何使此检查防弹?

我知道abuint64_t会延迟这种误报。 但它仍然无法完全解决此问题。

====

===========让我澄清一点:计数器用于跟踪内存分配,这个问题在 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

我认为您误解了代码中的注释:

我们除以二以免溢出iter_c * 2.

无论值来自何处,写入a/2是安全的,但写入a*2是不安全的。无论您使用哪种无符号类型,您始终可以将数字除以 2,而乘法可能会导致溢出。

如果条件是这样写的:

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

那么大约一半的输入会导致错误的条件。话虽如此,如果您担心计数器溢出,您可以将它们包装在一个类中:

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;}
}

请注意,要完全避免溢出,您必须采取其他措施,但如果ab或多或少地增加相同的量,这可能已经没问题了。

发布的代码实际上似乎没有使用可能环绕的计数器。

代码中的注释说的是,比较a/2 > ba > 2*b更安全,因为后者可能会溢出,而前者则不能。a的类型尤其如此,大于b的类型。

注意溢出发生时。

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;
}

每个a, b相互依赖地具有 232+ 1 个不同的状态,反映值和条件增量。 不知何故,需要的不仅仅是uint32_t的信息。 可以使用uint64_t,变体代码路径或辅助变量,如此处的bool

通过强制值同时换行来规范化值。 在它们包装时保持两者之间的差异。

尝试这样的事情;

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;
}

如果即使使用 64 位也不够,那么您需要编写自己的"var increase"方法,而不是重载++运算符(如果您不小心,这可能会弄乱您的代码(。
该方法只会将 var 重置为"0"或其他有意义的值。

如果您的目的是确保动作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;
}

在许多情况下,可能需要减少递增scaled_y_count时使用的比较中的常量,以限制可以"存储"的action_y操作数。 但是,在操作以 2:1 的比例保持接近平衡的情况下,上述方法应该精确地起作用,即使操作数量超过uint32_t的范围。