【问题标题】:Is it dangerous to rely on overflow?依赖溢出有危险吗?
【发布时间】:2013-12-06 16:32:56
【问题描述】:

以下是我在我们的固件中找到的延迟功能。它看起来有点危险,或者至少让读者感到困惑。

全局变量

static int32u masterTimerCounter; // 32-bit unsigned timer counter

系统滴答中断处理程序

/* Called Every Millisecond */
void sysTickIrqHandler(void)
{
    masterTimerCounter++;
}

设置定时器过期功能

void setTimerExpiration(int32u *timerExpiration, int32u delay)
{
    *timerExpiration = masterTimerCounter + delay;
}

检查定时器是否过期功能

boolean timerExpired(int32u timerExpiration, int32u delay)
{
    if((masterTimerCounter - timerExpiration) < delay)
        return TRUE; // Timer has expired
    else
        return FALSE; // Timer still active
}

设置计时器到期并阻止直到计时器到期

int32u timerExpiration;
setTimerExpiration(&timerExpiration, 15); // Set expiration timer to 15 milliseconds
while(!timerExpired(timerExpiration, 15) // Block until timer has expired
    continue; 

问题

正如您在timerExpired() 中看到的那样,masterTimerCounter 减去了timerExpiration。如果计时器还没有到期,计算将产生一个非常大的数字(因为两个操作数都是无符号数字)。当计时器到期时,计算将产生一个小于延迟量的值。

虽然这似乎工作正常,但它似乎可能很危险,或者至少会让读者感到困惑(我不得不阅读几遍才能理解最初程序员的意图)。

如果我必须写类似这样的东西,我会定义timerExpired函数如下:

boolean timerExpired(int32u timerExpiration)
{
    if(timerExpiration > masterTimerCounter)
        return FALSE; // Timer still active
    else
        return TRUE; // Timer has expired
}

我应该重新定义'timerExpired()`吗?

注意:为了保护无辜,函数和变量名称已更改。

【问题讨论】:

  • 那你可以忽略delay吗?
  • timerExpired 将延迟作为参数在这里实际上没有意义。
  • 如果我重新定义timerExpired()delay 真的不再需要了。
  • 实际上,您删除了 delay 参数。但是从提供的代码中并没有证明它是无用的。
  • @SChepurin 通过我的重新定义,它不再依赖较小的 unsigned 数字被较大的 unsigned 数字减去,从而产生巨大的 unsigned 数字(大于 @987654338 的数字@.

标签: c++ c timer delay counter


【解决方案1】:

请注意,原始逻辑类似于:是过去的绝对到期时间,但小于一个完整的延迟期之前。也许我们可以松散地表达它,就像这个计时器最近触发了

你修改的逻辑只是是过去的绝对过期时间,不一样。


您可以简单地通过在不等式的每一边添加timerExpiration 来避免下溢的风险:

boolean timerExpired(int32u timerExpiration, int32u delay)
{
// WAS: (masterTimerCounter - timerExpiration) < delay
    if(masterTimerCounter < timerExpiration + delay)
        return TRUE; // Timer has expired
    else
        return FALSE; // Timer still active
}

但这会改变行为,因为您说如果masterTimerCounter &lt; timerExpiration,原件将始终为假。您可以通过显式检查来获得原始行为,而不会出现令人困惑的下溢:

boolean timerExpired(int32u timerExpiration, int32u delay)
{
    if(masterTimerCounter > timerExpiration &&       // did it expire ...
       masterTimerCounter < timerExpiration + delay) // ... recently?
        return TRUE; // Timer has expired
    else
        return FALSE; // Timer still active
}

【讨论】:

    【解决方案2】:

    那个固件代码没有意义。

    int32u expire;
    setTimerExpiration(&expire, 0);
    timerExpired(expire, 0); // is always false, unless the timer overflows 
    

    【讨论】:

    • 延迟 0 永远不可能是真的,但问题中的延迟 15 可能是真的
    【解决方案3】:

    您的方式的问题是,如果 masterTimerCounter + delay 导致 32 位 int 翻转,那么 timerExpired 测试会立即通过。

    我认为在可能发生翻转的情况下执行整数计时器的最直接方法是这样的:

    void startTimer(int32u *timerValue)
    {
        *timerValue = masterTimerCounter;
    }
    

    检查定时器是否过期功能

    boolean timerExpired(int32u timerVal, int32u delay)
    {
        if ((masterTimerCounter - timerVal) >= delay)
            return TRUE; // Timer has expired
        else
            return FALSE; // Timer still active
    }
    

    用法:

    int32u timer;
    startTimer(&timer); // Start timing
    while(!timerExpired(timer, 15) // Block for 15 ticks
        continue;
    

    即使timerExpired 中的减法下溢,这也会返回正确的结果。

    【讨论】:

    • 拥有 unsigned int 翻转不是我的方式,而是现在的方式。我的问题是,这是否危险,如果是,我应该改变它。我认为我应该改变它的方式在我的问题的底部。
    • 假设 masterTimer 为 2^16-10,而您调用它时延迟为 15。您能保证您的系统不会连续运行那么多毫秒吗?
    • masterTimerCounter 实际上是一个 32 位无符号数。它仅在引导加载程序中运行。引导加载程序完成后,应用程序将启动。 masterTimerCounter == 0xFFFFFFFF == 4294967295 ms == 49.7 days
    • 好的,所以 49.7 天对于引导加载程序来说有点长。 (我最后一次做这样的事情是使用 16 位定时器)。尽管如此,在计时器翻转的情况下,原始代码是安全的,而您的建议不是。
    猜你喜欢
    • 1970-01-01
    • 2015-07-11
    • 1970-01-01
    • 2018-03-22
    • 1970-01-01
    • 2011-12-31
    • 2013-06-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多