当GetTickCount()包装时会发生什么?

时间:2009-04-07 22:59:08

标签: c++ c windows winapi

如果一个帖子正在做这样的事情:

const DWORD interval = 20000;
DWORD ticks = GetTickCount();
while(true)
{
    DoTasksThatTakeVariableTime();

    if( GetTickCount() - ticks > interval )
    {
        DoIntervalTasks();
        ticks = GetTickCount();
    }
}

最终,当值不适合DWORD时,ticks将会换行。

我和同事一直在讨论这个问题。我们中的一个人认为,当发生换行时,代码仍会表现得很“漂亮”,因为减法操作也会换行。我们其他人认为它并不总是有效,特别是如果间隔很大。

谁是对的,为什么?

感谢。

10 个答案:

答案 0 :(得分:13)

来自the docs

  

经过的时间存储为DWORD   值。因此,时间会结束   如果系统运行,则大约为零   连续49.7天。避免   这个问题,请使用GetTickCount64。   否则,检查溢出   比较时间的条件。

然而,DWORD没有签名 - 所以你应该没问题。 0 - “非常大的数字”=“小数字”(假设您没有激活溢出检查,当然)。我之前有一个编辑建议你得到一个负数,但那是在我考虑到DWORD未签名之前。

如果操作在 49.7天之后仍然存在问题,那么你仍会遇到问题。这对你来说可能不是问题;)

测试的一种方法是删除GetTickCount()方法,这样你就可以在明确地将其包装的地方编写单元测试。再说一次,如果你真的只是怀疑算术部分,你可以很容易地为它编写单元测试:)真的,这个数字来自系统时钟的事实几乎是不相关的,只要你知道它的行为。包装 - 并且在文档中指定。

答案 1 :(得分:10)

只要:

,没有什么不好的事情发生
  • 您减去DWORD,而不是先转换为其他类型。

  • 你想要的时间不会超过49.7天。

这是因为无符号算术溢出在C中定义良好,包装行为完全符合我们的要求。

DWORD t1, t2;
DWORD difference;

t1 = GetTickCount();
DoSomethingTimeConsuming();
t2 = GetTickCount();

t2 - t1将生成正确的值,即使GetTickCount包围。在进行减法操作之前,请勿将t2t1转换为其他类型(例如intdouble)。

如果编程语言将溢出视为错误,则无效。如果DoSomethingTimeConsuming()的时间超过49.7天,它也将无效。不幸的是,您只能通过查看t2t1 GetTickCount缠绕的次数来判断。


让我们从通常情况开始,没有发生回旋:

t1 = 13487231
t2 = 13492843

这里,t2 - t1 = 5612,这意味着操作大约需要五秒钟。

现在考虑一项花费很短时间的操作,但GetTickCount确实包含了这些操作:

t1 = 4294967173
t2 = 1111

操作耗时1234毫秒,但计时器缠绕,1111 - 4294967173-4294966062的虚假值。我们将做什么?

嗯,模2 32 ,减法的结果也包围了:

(DWORD)-4294966062 == (DWORD)1234

最后,考虑边缘情况,其中操作接近 2 32 毫秒,但不完全:

t1 = 2339189280
t2 = 2339167207

在这里,GetTickCount缠绕着,然后回到原来的位置。

现在t2 - t1会产生看似虚假的4294945223值。那是因为这是操作实际花费的时间!

一般来说:

(base + offset) - base ≡ offset mod 2^32

答案 2 :(得分:7)

如果要测试GetTickCount()换行时会发生什么,可以启用Application Verifier的TimeRollOver测试。

来自Using Application Verifier Within Your Software Development Lifecycle

  

TimeRollOver 强制GetTickCount和TimeGetTime API比平时更快地翻转。这使应用程序可以更轻松地测试它们对时间翻转的处理。

答案 3 :(得分:4)

我建议计算两个滴答之间的实际经过时间,而不是依靠编译器为你处理它:

const DWORD interval = 20000;

#define TICKS_DIFF(prev, cur) ((cur) >= (prev)) ? ((cur)-(prev)) : ((0xFFFFFFFF-(prev))+1+(cur))

DWORD ticks = GetTickCount();
while(true)
{
    DoTasksThatTakeVariableTime();

    DWORD curticks = GetTickCount();
    if( TICKS_DIFF(ticks, curticks) > interval )
    {
        DoIntervalTasks();
        ticks = GetTickCount();
    }
}

答案 4 :(得分:2)

我最近遇到了这个问题。我正在处理的代码在一堆地方使用GetTickCount()来确定程序是否在特定任务上花费了太多时间,如果是这样,它将超时该任务并重新安排它以便以后执行。会发生的是,如果GetTickCount()在其中一个测量周期中换行,则会导致代码过早超时。这是一项不断运行的服务,因此每49天就会有一次轻微的打嗝。

我通过编写一个在内部使用GetTickCount()的计时器类来修复它,但是当值包装并对其进行补偿时检测到它。

答案 5 :(得分:1)

当然,您需要处理此剔除问题。

Linux内核使用以下技巧处理此类剔除问题:

  

#define time_after(a,b)((long)(b) - (long)(a)< 0))

这个想法是无符号转换为签名并比较它们的值,那么只有当| a-b |< 2 ^ 30,然后换行不会影响结果。

你可以试试这个技巧,并了解其工作原理。

由于DWORD也是unsigned int,因此这个技巧也适用于windows。

所以你的代码可能就像:

  

const DWORD interval = 20000;

     

DWORD ticks = GetTickCount()+ interval;

     

while(true){

DoTasksThatTakeVariableTime();

if(time_after(ticks, GetTickCount())
{
    DoIntervalTasks();
    ticks = GetTickCount() + interval;
} 
     

}

仅当间隔小于0x2 ^ 30时才有效。

答案 6 :(得分:1)

你可以测试它;) - 我在这里有一个简单的测试应用程序,它将启动一个应用程序并在其中挂钩GetTickCount(),以便您可以从测试应用程序的GUI控制它。我把它写成一些应用程序中的GetTickCount()调用并不容易。

TickShifter是免费的,可在此处获取:http://www.lenholgate.com/blog/2006/04/tickshifter-v02.html

我在撰写一系列关于测试驱动开发的文章时写了这篇文章,该文章使用了一些以破解方式使用GetTickCount()的代码。

如果您有兴趣,请在此处提供文章:http://www.lenholgate.com/blog/2004/05/practical-testing.html

但是,总而言之,您的代码将有效......

答案 7 :(得分:1)

发生了大爆炸。 哦,对不起,一声巨响。

答案 8 :(得分:1)

这篇文章帮助了我,但我认为有一个错误:

#define TICKS_DIFF(prev, cur) ((cur) >= (prev)) ? ((cur)-(prev)) : ((0xFFFFFFFF-(prev))+(cur))

当我在环绕点测试时,我发现它已经被1点关闭了。

对我有用的是:

define TICKS_DIFF(prev, cur) ((cur) >= (prev)) ? ((cur)-(prev)) : ((0xFFFFFFFF-(prev))+(cur)+1)

答案 9 :(得分:1)

我知道这几乎是不相关的,将近11年之后,并且从Vista开始就包含了GetTickCount64(),但是这是我从那时起一直在我的工具中使用的一些帮助程序代码,我曾经经常使用它这是一个问题。

inline DWORD GetElapsed(DWORD from, DWORD to = ::GetTickCount())
{
  if (from < to)        //check for wrap around condition
      return (to - from);
  else
      return ((0xFFFFFFFFL - from) + 1 + to);
}

用法

DWORD start = ::GetTickCount();

// Some time later

DWORD elapsed = GetElapsed(start);

担心的不是您的操作可能不会花费更长的49.7天,而是滴答计数器可能会在您的操作过程中翻转,从而使经过时间的计算变得不可靠。

当然,由于GetTickCount64(),现在所有都不相关